"""
应用库选项卡
提供应用的创建、删除和可视化流程编辑功能
"""
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import json
import os
import uuid
from typing import Dict, Any, List, Tuple
import requests
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from utils.result_formatter import result_formatter


class FlowNode:
    """流程节点类，用于表示应用中的各个处理步骤"""
    def __init__(self, node_id=None, node_type="input", name="未命名节点", x=0, y=0):
        self.node_id = node_id or str(uuid.uuid4())[:8]
        self.node_type = node_type  # input, process, output
        self.name = name
        self.x = x
        self.y = y
        self.properties = {}
        self.connections = []  # 存储连接的节点ID和端口信息
        self.return_value = None  # 当前节点的返回值
        self.default_return_value = None  # 默认返回值，当节点无法产生有效输出时使用

    def to_dict(self):
        """将节点转换为字典格式"""
        return {
            "id": self.node_id,
            "type": self.node_type,
            "name": self.name,
            "x": self.x,
            "y": self.y,
            "properties": self.properties,
            "connections": self.connections,
            "default_return_value": self.default_return_value
        }

    @classmethod
    def from_dict(cls, data):
        """从字典创建节点实例"""
        node = cls(
            node_id=data.get("id"),
            node_type=data.get("type", "input"),
            name=data.get("name", "未命名节点"),
            x=data.get("x", 0),
            y=data.get("y", 0)
        )
        node.properties = data.get("properties", {})
        node.connections = data.get("connections", [])
        node.default_return_value = data.get("default_return_value", None)
        return node
        
    def get_upstream_nodes(self, flow):
        """获取上游连接的节点列表"""
        upstream_nodes = []
        for node_id, node in flow.nodes.items():
            for conn in node.connections:
                if conn["target_id"] == self.node_id:
                    upstream_nodes.append(node)
        return upstream_nodes
        
    def get_upstream_return_value(self, flow):
        """获取上游节点的返回值"""
        upstream_nodes = self.get_upstream_nodes(flow)
        if upstream_nodes:
            # 简单实现：返回第一个上游节点的返回值或默认返回值
            node = upstream_nodes[0]
            return node.return_value if node.return_value is not None else node.default_return_value
        return None


class ApplicationFlow:
    """应用流程类，用于管理应用中的所有节点和连接"""
    def __init__(self, app_id=None, app_name="未命名应用"):
        self.app_id = app_id or str(uuid.uuid4())[:8]
        self.app_name = app_name
        self.nodes = {}
        self.created_at = ""
        self.updated_at = ""

    def add_node(self, node):
        """添加一个节点"""
        self.nodes[node.node_id] = node
        
    def get_upstream_nodes_for(self, target_node_id):
        """获取指定节点的所有上游节点"""
        upstream_nodes = []
        for node_id, node in self.nodes.items():
            for conn in node.connections:
                if conn["target_id"] == target_node_id:
                    upstream_nodes.append(node)
        return upstream_nodes
        
    def get_node_chain(self, start_node_id):
        """获取从起始节点开始的完整节点链，用于数据流处理"""
        chain = []
        visited = set()
        
        def traverse(node_id):
            if node_id in visited:
                return
            visited.add(node_id)
            
            # 先处理上游节点
            upstream_nodes = self.get_upstream_nodes_for(node_id)
            for node in upstream_nodes:
                traverse(node.node_id)
                
            # 再添加当前节点
            if node_id in self.nodes:
                chain.append(self.nodes[node_id])
        
        traverse(start_node_id)
        return chain

    def remove_node(self, node_id):
        """删除一个节点"""
        if node_id in self.nodes:
            del self.nodes[node_id]
            # 同时删除与该节点相关的所有连接
            for n_id, node in self.nodes.items():
                node.connections = [conn for conn in node.connections if conn["target_id"] != node_id]

    def add_connection(self, source_id, target_id, source_port="out", target_port="in"):
        """添加两个节点之间的连接"""
        if source_id in self.nodes and target_id in self.nodes:
            # 检查连接是否已存在
            for conn in self.nodes[source_id].connections:
                if conn["target_id"] == target_id and conn["target_port"] == target_port:
                    return False
            
            self.nodes[source_id].connections.append({
                "target_id": target_id,
                "target_port": target_port,
                "source_port": source_port
            })
            return True
        return False

    def to_dict(self):
        """将流程转换为字典格式"""
        return {
            "app_id": self.app_id,
            "app_name": self.app_name,
            "nodes": {node_id: node.to_dict() for node_id, node in self.nodes.items()},
            "created_at": self.created_at,
            "updated_at": self.updated_at
        }

    @classmethod
    def from_dict(cls, data):
        """从字典创建流程实例"""
        flow = cls(
            app_id=data.get("app_id"),
            app_name=data.get("app_name", "未命名应用")
        )
        flow.created_at = data.get("created_at", "")
        flow.updated_at = data.get("updated_at", "")
        
        # 加载节点
        for node_id, node_data in data.get("nodes", {}).items():
            node = FlowNode.from_dict(node_data)
            flow.add_node(node)
            
        return flow


class FlowCanvas(tk.Canvas):
    """流程画布类，用于可视化显示和编辑应用流程"""
    def __init__(self, parent, flow_manager, **kwargs):
        super().__init__(parent, **kwargs)
        self.parent = parent
        self.flow_manager = flow_manager
        self.current_flow = None
        self.selected_node = None
        self.selected_connection = None  # 新增：用于存储选中的连接线
        self.dragging_node = None
        self.drag_data = {"x": 0, "y": 0}
        self.connection_line = None
        self.connection_source = None
        
        # 绑定事件
        self.bind("<Button-1>", self.on_canvas_click)
        self.bind("<B1-Motion>", self.on_canvas_drag)
        self.bind("<ButtonRelease-1>", self.on_canvas_release)
        self.bind("<Double-Button-1>", self.on_canvas_double_click)
        # 绑定键盘事件，确保画布能获取焦点
        self.focus_set()
        self.bind("<KeyPress-Delete>", self.on_delete_press)
        
        # 配置画布样式
        self.configure(background="#f0f0f0", highlightthickness=0)
        
    def set_flow(self, flow):
        """设置当前要显示的流程"""
        self.current_flow = flow
        self.redraw_flow()
        
    def redraw_flow(self):
        """重新绘制流程"""
        self.delete("all")
        if not self.current_flow:
            return
            
        # 绘制连接线
        # print("开始绘制连接线...")
        connection_id = 0
        for node_id, node in self.current_flow.nodes.items():
            # print(f"节点 {node_id} 的连接数: {len(node.connections)}")
            for conn in node.connections:
                if conn["target_id"] in self.current_flow.nodes:
                    source_node = node
                    target_node = self.current_flow.nodes[conn["target_id"]]
                    
                    # 计算连接线的起点和终点 - 修正位置使其与端口对齐
                    # 起点：输出端口中心
                    start_x = source_node.x + 200
                    start_y = source_node.y + 50
                    # 终点：输入端口中心
                    end_x = target_node.x
                    end_y = target_node.y + 50
                    
                    # 为连接线创建唯一ID
                    conn_tag = f"conn_{connection_id}"
                    connection_id += 1
                    
                    # 检查是否是选中的连接线
                    is_selected = False
                    if self.selected_connection and self.selected_connection == conn_tag:
                        is_selected = True
                    
                    # 绘制连接线，根据是否选中设置不同的样式
                    line_color = "#FF0000" if is_selected else "#666666"
                    line_width = 3 if is_selected else 2
                    
                    self.create_line(start_x, start_y, end_x, end_y, 
                                   fill=line_color, width=line_width, arrow=tk.LAST, tags=("connection", conn_tag))
        
        # 绘制节点
        for node_id, node in self.current_flow.nodes.items():
            # 根据节点类型设置不同的颜色
            if node.node_type == "input":
                color = "#4CAF50"
            elif node.node_type == "process":
                color = "#2196F3"
            else:  # output
                color = "#FF9800"
                
            # 绘制节点矩形
            rect = self.create_rectangle(
                node.x, node.y, node.x + 200, node.y + 100, 
                fill=color, outline="#333333", width=2, tags=("node", node_id)
            )
            
            # 绘制节点名称
            self.create_text(
                node.x + 100, node.y + 50, 
                text=node.name, fill="white", font=("Arial", 12, "bold"), tags=("node_text", node_id)
            )
            
            # 绘制端口，扩大尺寸使点击更容易
            self.create_oval(node.x - 10, node.y + 40, node.x + 10, node.y + 60, 
                           fill="#333333", tags=("in_port", node_id))
            self.create_oval(node.x + 190, node.y + 40, node.x + 210, node.y + 60, 
                           fill="#333333", tags=("out_port", node_id))
        
    def on_canvas_click(self, event):
        """处理画布点击事件"""
        # 先检查是否点击在节点区域内
        node_clicked = None
        if self.current_flow:
            for node_id, node in self.current_flow.nodes.items():
                # 计算节点的边界
                node_left = node.x
                node_right = node.x + 200
                node_top = node.y
                node_bottom = node.y + 100
                
                # 检查点击是否在节点边界内
                if (node_left <= event.x <= node_right and 
                    node_top <= event.y <= node_bottom):
                    node_clicked = node_id
                    break
        
        # 如果没有点击在任何节点上，检查是否点击在连接线上
        connection_clicked = None
        if not node_clicked and self.current_flow:
            closest_items = self.find_closest(event.x, event.y)
            if closest_items:
                item = closest_items[0]
                tags = self.gettags(item)
                # 检查是否点击在连接线上
                if "connection" in tags:
                    # 找到连接ID
                    for tag in tags:
                        if tag.startswith("conn_"):
                            connection_clicked = tag
                            break
        
        # 如果点击了节点
        if node_clicked:
            self.selected_node = node_clicked
            self.selected_connection = None
            
            # 高亮选中的节点
            self.redraw_flow()
            node = self.current_flow.nodes[node_clicked]
            self.create_rectangle(
                node.x - 5, node.y - 5, node.x + 205, node.y + 105, 
                fill="", outline="#FFD700", width=3, tags="selected"
            )
            
            # 准备拖动
            self.dragging_node = node_clicked
            self.drag_data["x"] = event.x
            self.drag_data["y"] = event.y
            return
        
        # 如果点击了连接线
        elif connection_clicked:
            self.selected_connection = connection_clicked
            self.selected_node = None
            self.redraw_flow()
            # 高亮选中的连接线
            # 注意：在redraw_flow方法中已处理连接线高亮
            return
        
        # 检查是否点击了输出端口
        closest_items = self.find_closest(event.x, event.y)
        if closest_items:
            item = closest_items[0]
            tags = self.gettags(item)
            
            # 点击输出端口，开始创建连接
            if "out_port" in tags and self.current_flow:
                # print("点击输出端口，尝试开始创建连接")
                # 找到节点ID
                node_id = None
                for tag in tags:
                    if tag != "out_port" and tag in self.current_flow.nodes:
                        node_id = tag
                        break
                
                if node_id:
                    # print(f"设置连接源为节点: {node_id}")
                    self.connection_source = node_id
                    self.connection_line = self.create_line(
                        event.x, event.y, event.x, event.y, 
                        fill="#666666", width=2, arrow=tk.LAST, dash=(5, 5)
                    )
                return
            
            # 点击输入端口，完成连接创建
            elif "in_port" in tags and self.connection_source and self.current_flow:
                # 找到目标节点ID
                target_id = None
                for tag in tags:
                    if tag != "in_port" and tag in self.current_flow.nodes:
                        target_id = tag
                        break
                
                if target_id and self.connection_source != target_id:
                    # print(f"尝试创建连接: 从 {self.connection_source} 到 {target_id}")
                    # 添加连接
                    success = self.current_flow.add_connection(self.connection_source, target_id)
                    # print(f"连接创建结果: {success}")
                    if success:
                        # 先清除临时连接
                        try:
                            if self.connection_line:
                                self.delete(self.connection_line)
                        except tk.TclError:
                            pass
                        self.connection_line = None
                        self.connection_source = None
                        
                        # 保存更新后的流程，确保连接信息不会丢失
                        self.flow_manager.save_application(self.current_flow)
                        
                        # 强制完全重绘，确保连接线显示
                        self.redraw_flow()
                        # 更新画布以立即显示变化
                        self.update_idletasks()
                else:
                    # 如果连接失败或点击了同一节点，也清除临时连接
                    try:
                        if self.connection_line:
                            self.delete(self.connection_line)
                    except tk.TclError:
                        pass
                    self.connection_line = None
                    self.connection_source = None
                return
        
        # 默认情况下，tags是空列表
        closest_items = self.find_closest(event.x, event.y)
        tags = []
        if closest_items:
            item = closest_items[0]
            tags = self.gettags(item)
            
        # 判断是否点击了输出端口、输入端口或节点
        is_out_port_clicked = "out_port" in tags
        is_in_port_clicked = "in_port" in tags
        is_node_clicked = "node" in tags
        
        # 点击输出端口，开始创建连接
        if is_out_port_clicked and self.current_flow:
            # print("点击输出端口，尝试开始创建连接")
            # 找到节点ID
            node_id = None
            for tag in tags:
                if tag != "out_port" and tag in self.current_flow.nodes:
                    node_id = tag
                    break
            
            if node_id:
                # print(f"设置连接源为节点: {node_id}")
                self.connection_source = node_id
                self.connection_line = self.create_line(
                    event.x, event.y, event.x, event.y, 
                    fill="#666666", width=2, arrow=tk.LAST, dash=(5, 5)
                )
            
        # 点击输入端口，完成连接创建
        # 当用户拖动连接并释放鼠标时，检测是否在目标节点附近
        elif (is_in_port_clicked or is_node_clicked) and self.connection_source and self.current_flow:
            # 找到目标节点ID
            target_id = None
            for tag in tags:
                if (tag != "in_port" and tag != "node") and tag in self.current_flow.nodes:
                    target_id = tag
                    break
            
            if target_id and self.connection_source != target_id:
                # print(f"尝试创建连接: 从 {self.connection_source} 到 {target_id}")
                # 添加连接
                success = self.current_flow.add_connection(self.connection_source, target_id)
                # print(f"连接创建结果: {success}")
                if success:
                    # 先清除临时连接
                    try:
                        if self.connection_line:
                            self.delete(self.connection_line)
                    except tk.TclError:
                        pass
                    self.connection_line = None
                    self.connection_source = None
                    
                    # 保存更新后的流程，确保连接信息不会丢失
                    self.flow_manager.save_application(self.current_flow)
                    
                    # 强制完全重绘，确保连接线显示
                    self.redraw_flow()
                    # 更新画布以立即显示变化
                    self.update_idletasks()
            else:
                # 如果连接失败或点击了同一节点，也清除临时连接
                try:
                    if self.connection_line:
                        self.delete(self.connection_line)
                except tk.TclError:
                    pass
                self.connection_line = None
                self.connection_source = None
            
        # 点击空白区域
        else:
            self.selected_node = None
            self.dragging_node = None
            self.redraw_flow()
            
            # 清除临时连接
            if self.connection_line:
                self.delete(self.connection_line)
                self.connection_line = None
                self.connection_source = None
        
    def on_canvas_drag(self, event):
        """处理画布拖动事件"""
        # 拖动节点
        if self.dragging_node and self.current_flow:
            node = self.current_flow.nodes[self.dragging_node]
            dx = event.x - self.drag_data["x"]
            dy = event.y - self.drag_data["y"]
            node.x += dx
            node.y += dy
            self.drag_data["x"] = event.x
            self.drag_data["y"] = event.y
            self.redraw_flow()
            
        # 拖动连接
        elif self.connection_line:
            try:
                # 检查连接线条是否存在并且coords返回了有效的坐标
                line_coords = self.coords(self.connection_line)
                if line_coords and len(line_coords) >= 2:
                    self.coords(self.connection_line, 
                               line_coords[0], 
                               line_coords[1], 
                               event.x, event.y)
                else:
                    # 如果连接线条无效，清除连接状态
                    self.connection_line = None
                    self.connection_source = None
            except tk.TclError:
                # 捕获Tcl错误，例如当连接线条已被删除时
                self.connection_line = None
                self.connection_source = None
        
    def on_canvas_double_click(self, event):
        """处理画布双击事件 - 用于编辑节点标签"""
        closest_items = self.find_closest(event.x, event.y)
        if not closest_items or not self.current_flow:
            return
        
        item = closest_items[0]
        tags = self.gettags(item)
        
        # 检查是否点击了节点文本
        if "node_text" in tags:
            # 找到节点ID
            node_id = None
            for tag in tags:
                if tag != "node_text" and tag in self.current_flow.nodes:
                    node_id = tag
                    break
            
            if node_id:
                node = self.current_flow.nodes[node_id]
                
                # 创建一个临时输入框来编辑标签
                def on_entry_confirm(event=None):
                    new_text = entry.get()
                    if new_text.strip():
                        node.name = new_text.strip()
                        self.flow_manager.save_application(self.current_flow)
                        self.redraw_flow()
                    self.delete(entry_window)
                
                # 创建输入窗口
                entry_window = self.create_window(
                    node.x + 100, node.y + 50,
                    window=tk.Entry(self, font=("Arial", 12, "bold"))
                )
                
                entry = self.winfo_children()[-1]
                entry.insert(0, node.name)
                entry.select_range(0, tk.END)
                entry.focus_set()
                entry.bind("<Return>", on_entry_confirm)
                entry.bind("<FocusOut>", on_entry_confirm)

    def on_delete_press(self, event=None):
        """处理删除键按下事件 - 用于删除选中的节点或连接线"""
        # 先尝试删除选中的连接线
        if self.selected_connection and self.current_flow:
            # 提示用户按Delete键删除选中的连接线
            # print("检测到已选中连接线，按Delete键删除")
            
            # 由于数据模型限制，我们需要实现一个简化但有效的删除方法
            # 我们将删除所有连接，并提示用户重新创建需要的连接
            # 这是一个临时解决方案，完整实现需要在数据模型中为连接添加唯一ID
            for node_id, node in list(self.current_flow.nodes.items()):
                node.connections = []
            
            # 保存更新后的流程
            self.flow_manager.save_application(self.current_flow)
            self.selected_connection = None
            self.redraw_flow()
            # print("连接线已删除，请重新创建需要的连接")
            return
        
        # 如果没有选中连接线，尝试删除选中的节点
        if self.selected_node and self.current_flow and self.selected_node in self.current_flow.nodes:
            # print("检测到已选中节点，按Delete键删除")
            # 删除选中的节点
            self.current_flow.remove_node(self.selected_node)
            self.flow_manager.save_application(self.current_flow)
            self.selected_node = None
            self.redraw_flow()
            # print("节点已删除")
        
        # 如果没有选中任何内容，提示用户
        elif event:
            # 提示用户("请先选中要删除的节点或连接线，然后按Delete键")
            messagebox.showinfo("提示", "请先选中要删除的节点或连接线，然后按Delete键")

    def on_canvas_release(self, event):
        """处理画布释放事件"""
        # print(f"释放鼠标按钮在位置: ({event.x}, {event.y})")
        self.dragging_node = None
        
        # 检查是否有连接正在创建中
        if self.connection_line and self.connection_source:
            # 尝试找到释放位置附近的目标节点
            target_id = None
            
            # 方法1: 检查最近的项目标签
            closest_items = self.find_closest(event.x, event.y)
            if closest_items:
                item = closest_items[0]
                item_tags = self.gettags(item)
                # print(f"释放位置最近的项目标签: {item_tags}")
                
                # 寻找节点ID标签
                for tag in item_tags:
                    if tag != "in_port" and tag != "out_port" and tag != "node" and tag != "current" and tag in self.current_flow.nodes:
                        target_id = tag
                        break
            
            # 方法2: 如果方法1没有找到，手动检查所有节点的位置
            if not target_id and self.current_flow:
                for node_id, node in self.current_flow.nodes.items():
                    # 检查鼠标是否在节点的输入端口区域内
                    in_port_x = node.x
                    in_port_y = node.y + 50
                    
                    # 定义一个更大的点击区域
                    click_area = 20  # 像素
                    if (abs(event.x - in_port_x) < click_area and 
                        abs(event.y - in_port_y) < click_area and 
                        node_id != self.connection_source):
                        target_id = node_id
                        # print(f"在节点 {node_id} 的输入端口附近检测到鼠标释放")
                        break
            
            # 如果找到目标节点，创建连接
            if target_id and target_id != self.connection_source:
                # print(f"尝试创建连接: 从 {self.connection_source} 到 {target_id}")
                # 添加连接
                success = self.current_flow.add_connection(self.connection_source, target_id)
                # print(f"连接创建结果: {success}")
                if success:
                    # 保存更新后的流程，确保连接信息不会丢失
                    self.flow_manager.save_application(self.current_flow)
            
            # 无论连接是否成功，清除临时连接
            try:
                self.delete(self.connection_line)
            except tk.TclError:
                pass
            self.connection_line = None
            self.connection_source = None
            
            # 强制完全重绘，确保连接线显示
            self.redraw_flow()
            # 更新画布以立即显示变化
            self.update_idletasks()
        
    def add_node(self, node_type, x, y):
        """添加一个新节点"""
        if not self.current_flow:
            return
            
        # 解析节点类型格式: type:subtype
        parts = node_type.split(":")
        base_type = parts[0]  # input, process, output
        subtype = parts[1] if len(parts) > 1 else "default"
        
        # 设置节点名称和默认属性
        name = "未命名节点"
        default_properties = {}
        
        if base_type == "input":
            if subtype == "image":
                name = "图像输入节点"
                default_properties = {"input_type": "image", "image_path": "", "resize_width": 640, "resize_height": 480}
            elif subtype == "video":
                name = "视频输入节点"
                default_properties = {"input_type": "video", "video_path": "", "frame_rate": 30}
            elif subtype == "camera":
                name = "摄像头输入节点"
                default_properties = {"input_type": "camera", "camera_id": 0, "resolution": "640x480"}
        elif base_type == "process":
            if subtype == "face_detection":
                name = "人脸检测节点"
                default_properties = {"process_type": "face_detection", "confidence_threshold": 0.7, "model": "default"}
            elif subtype == "face_recognition":
                name = "人脸识别节点"
                default_properties = {"process_type": "face_recognition", "database": "default", "similarity_threshold": 0.6}
            elif subtype == "feature_extraction":
                name = "特征提取节点"
                default_properties = {"process_type": "feature_extraction", "feature_dim": 128, "normalization": True}
            elif subtype == "api_call":
                name = "API调用节点"
                default_properties = {"process_type": "api_call", "api_url": "", "http_method": "GET", "headers": "{\"Content-Type\": \"application/json\"}"}
        elif base_type == "output":
            if subtype == "display":
                name = "图像显示节点"
                default_properties = {"output_type": "display", "show_bounding_box": True, "show_labels": True}
            elif subtype == "file":
                name = "文件保存节点"
                default_properties = {"output_type": "file", "output_path": "./output", "format": "jpg", "overwrite": False}
            elif subtype == "database":
                name = "数据库存储节点"
                default_properties = {"output_type": "database", "db_name": "face_db", "collection": "faces"}
            elif subtype == "export":
                name = "结果导出节点"
                default_properties = {"output_type": "export", "export_format": "json", "export_path": "./exports", "include_metadata": True}
            elif subtype == "message":
                name = "消息输出节点"
                default_properties = {"output_type": "message", "message_format": "{result}", "show_dialog": False, "log_to_console": True}
        
        # 创建新节点
        node = FlowNode(node_type=base_type, name=name, x=x, y=y)
        node.properties = default_properties
        self.current_flow.add_node(node)
        self.redraw_flow()
        return node.node_id
        
    def delete_selected_node(self):
        """删除选中的节点"""
        if self.selected_node and self.current_flow:
            self.current_flow.remove_node(self.selected_node)
            self.selected_node = None
            self.redraw_flow()
    
    def delete_selected_connection(self):
        """删除选中的连接线"""
        if self.selected_connection and self.current_flow:
            # 首先需要找到连接的源节点和目标节点
            # 我们需要重新遍历所有节点和连接来找到匹配的连接
            connection_deleted = False
            for source_node_id, source_node in list(self.current_flow.nodes.items()):
                # 创建一个新的连接列表，不包含我们要删除的连接
                new_connections = []
                for conn in source_node.connections:
                    # 检查这个连接是否是我们要删除的连接
                    # 我们需要重新绘制连接线并确定索引
                    # 这是一个简化的方法，可能不是100%准确
                    target_node = self.current_flow.nodes.get(conn['target_id'])
                    if target_node:
                        # 重新计算连接ID，这需要与redraw_flow中的逻辑保持一致
                        # 这是一个近似方法
                        conn_index = 0
                        found = False
                        for n_id, n in self.current_flow.nodes.items():
                            if n_id == source_node_id:
                                for c in n.connections:
                                    if c['target_id'] == conn['target_id']:
                                        if f"conn_{conn_index}" == self.selected_connection:
                                            found = True
                                            break
                                    conn_index += 1
                            else:
                                conn_index += len(n.connections)
                            if found:
                                break
                        if not found:
                            new_connections.append(conn)
                    else:
                        new_connections.append(conn)
                
                # 如果连接列表发生变化，说明找到了要删除的连接
                if len(new_connections) != len(source_node.connections):
                    source_node.connections = new_connections
                    connection_deleted = True
                    break
            
            # 如果没有找到连接，使用一个更简单的方法：尝试删除选中连接ID对应的所有可能连接
            if not connection_deleted:
                # 这是一个备用方法，可能会删除错误的连接
                # 但在大多数情况下应该能工作
                conn_index = int(self.selected_connection.replace('conn_', '')) if self.selected_connection.startswith('conn_') else 0
                current_index = 0
                for source_node_id, source_node in list(self.current_flow.nodes.items()):
                    for i, conn in enumerate(list(source_node.connections)):
                        if current_index == conn_index:
                            source_node.connections.pop(i)
                            connection_deleted = True
                            break
                        current_index += 1
                    if connection_deleted:
                        break
            
            self.selected_connection = None
            self.redraw_flow()


class FlowManager:
    """流程管理器，用于管理所有应用流程"""
    def __init__(self, data_dir="data/applications"):
        self.data_dir = data_dir
        self.applications = {}
        
        # 确保数据目录存在
        if not os.path.exists(self.data_dir):
            os.makedirs(self.data_dir)
            
        # 加载所有应用
        self.load_all_applications()
        
    def load_all_applications(self):
        """加载所有应用"""
        self.applications = {}
        if not os.path.exists(self.data_dir):
            return
            
        for filename in os.listdir(self.data_dir):
            if filename.endswith(".json"):
                try:
                    with open(os.path.join(self.data_dir, filename), "r", encoding="utf-8") as f:
                        data = json.load(f)
                        app = ApplicationFlow.from_dict(data)
                        self.applications[app.app_id] = app
                except Exception as e:
                    print(f"加载应用 {filename} 失败: {e}")
                    
    def save_application(self, app):
        """保存应用"""
        if not os.path.exists(self.data_dir):
            os.makedirs(self.data_dir)
            
        filename = os.path.join(self.data_dir, f"app_{app.app_id}.json")
        try:
            with open(filename, "w", encoding="utf-8") as f:
                json.dump(app.to_dict(), f, ensure_ascii=False, indent=2)
            return True
        except Exception as e:
            print(f"保存应用失败: {e}")
            return False
            
    def delete_application(self, app_id):
        """删除应用"""
        if app_id in self.applications:
            filename = os.path.join(self.data_dir, f"app_{app_id}.json")
            if os.path.exists(filename):
                os.remove(filename)
            del self.applications[app_id]
            return True
        return False
        
    def create_application(self, app_name):
        """创建新应用"""
        import datetime
        app = ApplicationFlow(app_name=app_name)
        app.created_at = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        app.updated_at = app.created_at
        self.applications[app.app_id] = app
        self.save_application(app)
        return app


class NodePalette(ttk.Frame):
    """节点调色板，用于选择要添加的节点类型"""
    def __init__(self, parent, canvas, **kwargs):
        super().__init__(parent, **kwargs)
        self.parent = parent
        self.canvas = canvas
        
        # 设置样式
        self.configure(padding="10")
        
        # 创建标题
        ttk.Label(self, text="节点类型", font=('SimHei', 12, "bold")).pack(pady=(0, 10))
        
        # 创建节点类型下拉选择框
        ttk.Label(self, text="选择节点类型:", font=('SimHei', 10)).pack(anchor="w", pady=(0, 5))
        
        # 定义节点类型选项
        self.node_types = [
            ("输入节点 - 图像", "input:image"),
            ("输入节点 - 视频", "input:video"),
            ("输入节点 - 摄像头", "input:camera"),
            ("处理节点 - 人脸检测", "process:face_detection"),
            ("处理节点 - 人脸识别", "process:face_recognition"),
            ("处理节点 - 特征提取", "process:feature_extraction"),
            ("处理节点 - API调用", "process:api_call"),
            ("输出节点 - 图像显示", "output:display"),
            ("输出节点 - 文件保存", "output:file"),
            ("输出节点 - 数据库存储", "output:database"),
            ("输出节点 - 结果导出", "output:export"),
            ("输出节点 - 消息提示", "output:message")  # 添加消息输出节点
        ]
        
        # 创建下拉选择框
        self.node_type_var = tk.StringVar(value="请选择节点类型")
        self.node_type_combo = ttk.Combobox(self, textvariable=self.node_type_var, 
                                           values=[item[0] for item in self.node_types], 
                                           state="readonly", width=25)
        self.node_type_combo.pack(fill="x", pady=5)
        self.node_type_combo.bind("<<ComboboxSelected>>", self.on_node_type_selected)
        
        # 添加节点按钮
        self.add_node_btn = ttk.Button(self, text="添加到画布", command=self.show_add_hint, state="disabled")
        self.add_node_btn.pack(fill="x", pady=10)
        
        # 提示信息
        self.hint_label = ttk.Label(self, text="请先选择节点类型", font=('SimHei', 9), foreground="#666666")
        self.hint_label.pack(pady=(5, 0))
        
        # 设置当前选中的节点类型
        self.selected_node_type = None
        
        # 绑定画布点击事件，用于添加节点
        self.canvas.bind("<Button-1>", self.on_canvas_click, add="+")
        
    def on_node_type_selected(self, event):
        """处理节点类型选择事件"""
        selected_text = self.node_type_var.get()
        for text, node_type in self.node_types:
            if text == selected_text:
                self.selected_node_type = node_type
                self.hint_label.config(text="请点击画布空白处添加节点")
                self.add_node_btn.config(state="normal")
                break
        
    def show_add_hint(self):
        """显示添加节点的提示"""
        if self.selected_node_type:
            self.hint_label.config(text="请点击画布空白处添加节点")
        
    def on_canvas_click(self, event):
        """处理画布点击事件，添加节点"""
        # 检查是否点击的是画布空白区域
        closest_items = self.canvas.find_closest(event.x, event.y)
        
        # 情况1: 画布上没有任何元素
        if not closest_items:
            if self.selected_node_type:
                self.canvas.add_node(self.selected_node_type, event.x - 100, event.y - 50)
                self.reset_selection()
            return
        
        # 情况2: 点击了画布上的元素，但不是节点或端口
        item = closest_items[0]
        tags = self.canvas.gettags(item)
        
        if "node" not in tags and "in_port" not in tags and "out_port" not in tags and self.selected_node_type:
            # 在点击位置添加新节点
            self.canvas.add_node(self.selected_node_type, event.x - 100, event.y - 50)
            self.reset_selection()
    
    def reset_selection(self):
        """重置节点类型选择"""
        self.selected_node_type = None
        self.node_type_var.set("请选择节点类型")
        self.add_node_btn.config(state="disabled")
        self.hint_label.config(text="请先选择节点类型")


class NodePropertiesEditor(ttk.Frame):
    """节点属性编辑器，用于编辑选中节点的属性"""
    def __init__(self, parent, canvas, **kwargs):
        super().__init__(parent, **kwargs)
        self.parent = parent
        self.canvas = canvas  # 保留原始的canvas引用
        self.current_node_id = None
        
        # 设置样式，确保固定宽度
        self.configure(padding="10", width=300)
        
        # 直接在主面板中创建标题
        self.title_label = ttk.Label(self, text="节点属性", font=('SimHei', 12, "bold"))
        self.title_label.pack(pady=(0, 10), fill="x")
        
        # 创建带滚动功能的属性编辑区域
        # 1. 创建滚动条
        self.scrollbar = ttk.Scrollbar(self, orient="vertical")
        
        # 2. 创建画布作为滚动区域
        self.scroll_canvas = tk.Canvas(self, yscrollcommand=self.scrollbar.set, highlightthickness=0, height=600)  # 设置最小高度
        self.scroll_canvas.pack(side="right", fill="both", expand=True, pady=(0, 10))
        #设置画布背景
        self.scroll_canvas.config(bg="#f0f0f0")
        # 3. 配置滚动条与画布的关联
        self.scrollbar.config(command=self.scroll_canvas.yview)
        self.scrollbar.pack(side="right", fill="y", pady=(0, 10))
        
        # 4. 创建属性框架并放置在画布上
        self.properties_frame = ttk.Frame(self.scroll_canvas)
        self.canvas_window = self.scroll_canvas.create_window((0, 0), window=self.properties_frame, anchor="nw")
        
        # 配置列权重
        self.properties_frame.grid_columnconfigure(0, weight=0, minsize=100)  # 标签列固定宽度
        self.properties_frame.grid_columnconfigure(1, weight=1)  # 输入列可扩展
        
        # 添加一个占位标签，确保面板有足够的高度
        self.placeholder_label = ttk.Label(self.properties_frame, text=" ")
        self.placeholder_label.grid(row=99, column=0, pady=5)
        
        # 5. 绑定事件以实现滚动
        self.properties_frame.bind("<Configure>", self._on_content_configure)
        self.scroll_canvas.bind("<Configure>", self._on_canvas_configure)
        
        # 6. 绑定鼠标滚轮事件（Windows）
        self.scroll_canvas.bind("<MouseWheel>", self._on_mousewheel)
        
        # 7. 绑定鼠标滚轮事件（Linux）
        self.scroll_canvas.bind("<Button-4>", self._on_mousewheel_linux)
        self.scroll_canvas.bind("<Button-5>", self._on_mousewheel_linux)
        
        # 绑定画布选择事件
        self.canvas.bind("<ButtonRelease-1>", self.on_node_selected, add="+")
        

            
    def _on_content_configure(self, event):
        """当内容框架大小变化时更新画布滚动区域"""
        # 更新画布的滚动区域以适应内容
        self.scroll_canvas.configure(scrollregion=self.scroll_canvas.bbox("all"))
        
    def _on_canvas_configure(self, event):
        """当画布大小变化时更新内容窗口大小"""
        # 确保内容窗口的宽度与画布一致
        width = event.width
        self.scroll_canvas.itemconfig(self.canvas_window, width=width)
        
    def _on_mousewheel(self, event):
        """处理Windows系统的鼠标滚轮事件"""
        self.scroll_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
        
    def _on_mousewheel_linux(self, event):
        """处理Linux系统的鼠标滚轮事件"""
        if event.num == 4:
            self.scroll_canvas.yview_scroll(-1, "units")
        elif event.num == 5:
            self.scroll_canvas.yview_scroll(1, "units")
            
    def on_node_selected(self, event):
        """处理节点选择事件"""
        if self.canvas.selected_node != self.current_node_id:
            self.current_node_id = self.canvas.selected_node
            self.update_properties()
            
    def update_properties(self):
        """更新属性编辑器"""
        # 清除现有控件（排除占位标签）
        for widget in self.properties_frame.winfo_children():
            if widget != self.placeholder_label:
                widget.destroy()
            
        # 如果没有选中节点，显示提示信息
        if not self.current_node_id or not self.canvas.current_flow:
            ttk.Label(self.properties_frame, text="未选择节点").pack(pady=10)
            return
            
        # 获取选中的节点
        node = self.canvas.current_flow.nodes.get(self.current_node_id)
        if not node:
            return
            
        # 更新标题
        self.title_label.config(text=f"节点属性: {node.name}")
        
        # 添加名称编辑框
        ttk.Label(self.properties_frame, text="节点名称:").grid(row=0, column=0, sticky="w", pady=5)
        name_var = tk.StringVar(value=node.name)
        name_entry = ttk.Entry(self.properties_frame, textvariable=name_var)
        name_entry.grid(row=0, column=1, sticky="ew", pady=5)
        name_entry.bind("<FocusOut>", lambda e: self.update_node_name(name_var.get()))
        
        # 添加节点类型显示
        ttk.Label(self.properties_frame, text="节点类型:").grid(row=1, column=0, sticky="w", pady=5)
        type_var = tk.StringVar(value=node.node_type)
        type_combo = ttk.Combobox(self.properties_frame, textvariable=type_var, 
                                  values=["input", "process", "output"], state="readonly")
        type_combo.grid(row=1, column=1, sticky="ew", pady=5)
        type_combo.bind("<<ComboboxSelected>>", lambda e: self.update_node_type(type_var.get()))
        
        # 根据节点类型添加不同的属性
        if node.node_type == "input":
            self._add_input_properties(node)
        elif node.node_type == "process":
            self._add_process_properties(node)
        elif node.node_type == "output":
            self._add_output_properties(node)
        
        # 添加节点返回值属性，所有节点类型共享
        self._add_return_value_properties(node)
            
    def _add_input_properties(self, node):
        """添加输入节点的属性"""
        # 输入类型
        ttk.Label(self.properties_frame, text="输入类型:").grid(row=2, column=0, sticky="w", pady=5)
        input_type_var = tk.StringVar(value=node.properties.get("input_type", "image"))
        input_type_combo = ttk.Combobox(self.properties_frame, textvariable=input_type_var, 
                                       values=["image", "video", "camera"], state="readonly")
        input_type_combo.grid(row=2, column=1, sticky="ew", pady=5)
        input_type_combo.bind("<<ComboboxSelected>>", lambda e: [
            self.update_node_property("input_type", input_type_var.get()),
            self.update_properties()
        ])
        
        # 根据输入类型显示不同的属性
        input_type = node.properties.get("input_type", "image")
        row = 3
        
        if input_type == "image":
            ttk.Label(self.properties_frame, text="图像路径:").grid(row=row, column=0, sticky="w", pady=5)
            image_path_var = tk.StringVar(value=node.properties.get("image_path", ""))
            image_path_entry = ttk.Entry(self.properties_frame, textvariable=image_path_var, width=30)
            image_path_entry.grid(row=row, column=1, sticky="ew", pady=5)
            image_path_entry.bind("<FocusOut>", lambda e: self.update_node_property("image_path", image_path_var.get()))
            row += 1
            
            # 图像尺寸
            ttk.Label(self.properties_frame, text="调整宽度:").grid(row=row, column=0, sticky="w", pady=5)
            resize_width_var = tk.StringVar(value=str(node.properties.get("resize_width", 640)))
            resize_width_entry = ttk.Entry(self.properties_frame, textvariable=resize_width_var, width=10)
            resize_width_entry.grid(row=row, column=1, sticky="ew", pady=5)
            resize_width_entry.bind("<FocusOut>", lambda e: self.update_node_property("resize_width", int(resize_width_var.get()) if resize_width_var.get().isdigit() else 640))
            row += 1
            
            ttk.Label(self.properties_frame, text="调整高度:").grid(row=row, column=0, sticky="w", pady=5)
            resize_height_var = tk.StringVar(value=str(node.properties.get("resize_height", 480)))
            resize_height_entry = ttk.Entry(self.properties_frame, textvariable=resize_height_var, width=10)
            resize_height_entry.grid(row=row, column=1, sticky="ew", pady=5)
            resize_height_entry.bind("<FocusOut>", lambda e: self.update_node_property("resize_height", int(resize_height_var.get()) if resize_height_var.get().isdigit() else 480))
            row += 1
        elif input_type == "video":
            ttk.Label(self.properties_frame, text="视频路径:").grid(row=row, column=0, sticky="w", pady=5)
            video_path_var = tk.StringVar(value=node.properties.get("video_path", ""))
            video_path_entry = ttk.Entry(self.properties_frame, textvariable=video_path_var, width=30)
            video_path_entry.grid(row=row, column=1, sticky="ew", pady=5)
            video_path_entry.bind("<FocusOut>", lambda e: self.update_node_property("video_path", video_path_var.get()))
            row += 1
            
            ttk.Label(self.properties_frame, text="帧率:").grid(row=row, column=0, sticky="w", pady=5)
            frame_rate_var = tk.StringVar(value=str(node.properties.get("frame_rate", 30)))
            frame_rate_entry = ttk.Entry(self.properties_frame, textvariable=frame_rate_var, width=10)
            frame_rate_entry.grid(row=row, column=1, sticky="ew", pady=5)
            frame_rate_entry.bind("<FocusOut>", lambda e: self.update_node_property("frame_rate", int(frame_rate_var.get()) if frame_rate_var.get().isdigit() else 30))
            row += 1
        elif input_type == "camera":
            ttk.Label(self.properties_frame, text="摄像头ID:").grid(row=row, column=0, sticky="w", pady=5)
            camera_id_var = tk.StringVar(value=str(node.properties.get("camera_id", 0)))
            camera_id_entry = ttk.Entry(self.properties_frame, textvariable=camera_id_var, width=10)
            camera_id_entry.grid(row=row, column=1, sticky="ew", pady=5)
            camera_id_entry.bind("<FocusOut>", lambda e: self.update_node_property("camera_id", int(camera_id_var.get()) if camera_id_var.get().isdigit() else 0))
            row += 1
            
            ttk.Label(self.properties_frame, text="分辨率:").grid(row=row, column=0, sticky="w", pady=5)
            resolution_var = tk.StringVar(value=node.properties.get("resolution", "640x480"))
            resolution_combo = ttk.Combobox(self.properties_frame, textvariable=resolution_var, 
                                           values=["640x480", "800x600", "1280x720", "1920x1080"], state="readonly")
            resolution_combo.grid(row=row, column=1, sticky="ew", pady=5)
            resolution_combo.bind("<<ComboboxSelected>>", lambda e: self.update_node_property("resolution", resolution_var.get()))
        
    def _add_process_properties(self, node):
        """添加处理节点的属性"""
        # 处理类型
        ttk.Label(self.properties_frame, text="处理类型:").grid(row=2, column=0, sticky="w", pady=5)
        process_type_var = tk.StringVar(value=node.properties.get("process_type", "face_detection"))
        process_type_combo = ttk.Combobox(self.properties_frame, textvariable=process_type_var, 
                                         values=["face_detection", "face_recognition", "feature_extraction", "api_call"], state="readonly")
        process_type_combo.grid(row=2, column=1, sticky="ew", pady=5)
        process_type_combo.bind("<<ComboboxSelected>>", lambda e: [
            self.update_node_property("process_type", process_type_var.get()),
            self.update_properties()
        ])
        
        # 根据处理类型显示不同的属性
        process_type = node.properties.get("process_type", "face_detection")
        row = 3
        
        if process_type == "face_detection":
            ttk.Label(self.properties_frame, text="置信度阈值:").grid(row=row, column=0, sticky="w", pady=5)
            confidence_var = tk.StringVar(value=str(node.properties.get("confidence_threshold", 0.7)))
            confidence_entry = ttk.Entry(self.properties_frame, textvariable=confidence_var, width=10)
            confidence_entry.grid(row=row, column=1, sticky="ew", pady=5)
            confidence_entry.bind("<FocusOut>", lambda e: self.update_node_property("confidence_threshold", float(confidence_var.get()) if confidence_var.get() else 0.7))
            row += 1
            
            ttk.Label(self.properties_frame, text="模型选择:").grid(row=row, column=0, sticky="w", pady=5)
            model_var = tk.StringVar(value=node.properties.get("model", "default"))
            model_combo = ttk.Combobox(self.properties_frame, textvariable=model_var, 
                                      values=["default", "fast", "accurate"], state="readonly")
            model_combo.grid(row=row, column=1, sticky="ew", pady=5)
            model_combo.bind("<<ComboboxSelected>>", lambda e: self.update_node_property("model", model_var.get()))
            row += 1
        elif process_type == "face_recognition":
            ttk.Label(self.properties_frame, text="数据库:").grid(row=row, column=0, sticky="w", pady=5)
            db_var = tk.StringVar(value=node.properties.get("database", "default"))
            db_entry = ttk.Entry(self.properties_frame, textvariable=db_var, width=20)
            db_entry.grid(row=row, column=1, sticky="ew", pady=5)
            db_entry.bind("<FocusOut>", lambda e: self.update_node_property("database", db_var.get()))
            row += 1
            
            ttk.Label(self.properties_frame, text="相似度阈值:").grid(row=row, column=0, sticky="w", pady=5)
            similarity_var = tk.StringVar(value=str(node.properties.get("similarity_threshold", 0.6)))
            similarity_entry = ttk.Entry(self.properties_frame, textvariable=similarity_var, width=10)
            similarity_entry.grid(row=row, column=1, sticky="ew", pady=5)
            similarity_entry.bind("<FocusOut>", lambda e: self.update_node_property("similarity_threshold", float(similarity_var.get()) if similarity_var.get() else 0.6))
            row += 1
            
            # 添加静默识别选项
            ttk.Label(self.properties_frame, text="静默识别(不弹出窗口):").grid(row=row, column=0, sticky="w", pady=5)
            silent_mode_var = tk.BooleanVar(value=node.properties.get("silent_mode", False))
            silent_mode_check = ttk.Checkbutton(self.properties_frame, variable=silent_mode_var)
            silent_mode_check.grid(row=row, column=1, sticky="w", pady=5)
            silent_mode_check.configure(command=lambda: self.update_node_property("silent_mode", silent_mode_var.get()))
            row += 1
        elif process_type == "feature_extraction":
            ttk.Label(self.properties_frame, text="特征维度:").grid(row=row, column=0, sticky="w", pady=5)
            dim_var = tk.StringVar(value=str(node.properties.get("feature_dim", 128)))
            dim_entry = ttk.Entry(self.properties_frame, textvariable=dim_var, width=10)
            dim_entry.grid(row=row, column=1, sticky="ew", pady=5)
            dim_entry.bind("<FocusOut>", lambda e: self.update_node_property("feature_dim", int(dim_var.get()) if dim_var.get().isdigit() else 128))
            row += 1
            
            ttk.Label(self.properties_frame, text="特征归一化:").grid(row=row, column=0, sticky="w", pady=5)
            norm_var = tk.BooleanVar(value=node.properties.get("normalization", True))
            norm_check = ttk.Checkbutton(self.properties_frame, variable=norm_var)
            norm_check.grid(row=row, column=1, sticky="w", pady=5)
            norm_check.configure(command=lambda: self.update_node_property("normalization", norm_var.get()))
            row += 1
        elif process_type == "api_call":
            # API调用节点属性
            ttk.Label(self.properties_frame, text="API URL:").grid(row=row, column=0, sticky="w", pady=5)
            api_url_var = tk.StringVar(value=node.properties.get("api_url", ""))
            api_url_entry = ttk.Entry(self.properties_frame, textvariable=api_url_var, width=30)
            api_url_entry.grid(row=row, column=1, sticky="ew", pady=5)
            api_url_entry.bind("<FocusOut>", lambda e: self.update_node_property("api_url", api_url_var.get()))
            row += 1
            
            # 参数引用帮助提示
            help_text = "提示: 可使用 {upstream.字段名} 格式引用上游节点返回值"
            ttk.Label(self.properties_frame, text=help_text, foreground="#666666", font=('SimHei', 9)).grid(row=row, column=0, columnspan=2, sticky="w", pady=3)
            row += 1
            
            ttk.Label(self.properties_frame, text="HTTP方法:").grid(row=row, column=0, sticky="w", pady=5)
            method_var = tk.StringVar(value=node.properties.get("http_method", "GET"))
            method_combo = ttk.Combobox(self.properties_frame, textvariable=method_var, 
                                       values=["GET", "POST", "PUT", "DELETE"], state="readonly")
            method_combo.grid(row=row, column=1, sticky="ew", pady=5)
            method_combo.bind("<<ComboboxSelected>>", lambda e: self.update_node_property("http_method", method_var.get()))
            row += 1
            
            ttk.Label(self.properties_frame, text="请求头(JSON):").grid(row=row, column=0, sticky="w", pady=5)
            headers_var = tk.StringVar(value=node.properties.get("headers", '{"Content-Type": "application/json"}'))
            headers_entry = ttk.Entry(self.properties_frame, textvariable=headers_var, width=30)
            headers_entry.grid(row=row, column=1, sticky="ew", pady=5)
            headers_entry.bind("<FocusOut>", lambda e: self.update_node_property("headers", headers_var.get()))
            row += 1
            
            ttk.Label(self.properties_frame, text="请求体(JSON):").grid(row=row, column=0, sticky="w", pady=5)
            body_var = tk.StringVar(value=node.properties.get("body", "{}"))
            body_entry = ttk.Entry(self.properties_frame, textvariable=body_var, width=30)
            body_entry.grid(row=row, column=1, sticky="ew", pady=5)
            body_entry.bind("<FocusOut>", lambda e: self.update_node_property("body", body_var.get()))
        
    def _add_output_properties(self, node):
        """添加输出节点的属性"""
        # 输出类型
        ttk.Label(self.properties_frame, text="输出类型:").grid(row=2, column=0, sticky="w", pady=5)
        output_type_var = tk.StringVar(value=node.properties.get("output_type", "display"))
        output_type_combo = ttk.Combobox(self.properties_frame, textvariable=output_type_var, 
                                        values=["display", "file", "database", "export", "message"], state="readonly")
        output_type_combo.grid(row=2, column=1, sticky="ew", pady=5)
        output_type_combo.bind("<<ComboboxSelected>>", lambda e: [
            self.update_node_property("output_type", output_type_var.get()),
            self.update_properties()
        ])
        
        # 根据输出类型显示不同的属性
        output_type = node.properties.get("output_type", "display")
        row = 3
        
        if output_type == "display":
            ttk.Label(self.properties_frame, text="显示边界框:").grid(row=row, column=0, sticky="w", pady=5)
            show_bbox_var = tk.BooleanVar(value=node.properties.get("show_bounding_box", True))
            show_bbox_check = ttk.Checkbutton(self.properties_frame, variable=show_bbox_var)
            show_bbox_check.grid(row=row, column=1, sticky="w", pady=5)
            show_bbox_check.configure(command=lambda: self.update_node_property("show_bounding_box", show_bbox_var.get()))
            row += 1
            
            ttk.Label(self.properties_frame, text="显示标签:").grid(row=row, column=0, sticky="w", pady=5)
            show_labels_var = tk.BooleanVar(value=node.properties.get("show_labels", True))
            show_labels_check = ttk.Checkbutton(self.properties_frame, variable=show_labels_var)
            show_labels_check.grid(row=row, column=1, sticky="w", pady=5)
            show_labels_check.configure(command=lambda: self.update_node_property("show_labels", show_labels_var.get()))
            row += 1
            
            ttk.Label(self.properties_frame, text="显示置信度:").grid(row=row, column=0, sticky="w", pady=5)
            show_conf_var = tk.BooleanVar(value=node.properties.get("show_confidence", True))
            show_conf_check = ttk.Checkbutton(self.properties_frame, variable=show_conf_var)
            show_conf_check.grid(row=row, column=1, sticky="w", pady=5)
            show_conf_check.configure(command=lambda: self.update_node_property("show_confidence", show_conf_var.get()))
            row += 1
        elif output_type == "file":
            ttk.Label(self.properties_frame, text="输出路径:").grid(row=row, column=0, sticky="w", pady=5)
            output_path_var = tk.StringVar(value=node.properties.get("output_path", "./output"))
            output_path_entry = ttk.Entry(self.properties_frame, textvariable=output_path_var, width=30)
            output_path_entry.grid(row=row, column=1, sticky="ew", pady=5)
            output_path_entry.bind("<FocusOut>", lambda e: self.update_node_property("output_path", output_path_var.get()))
            row += 1
            
            ttk.Label(self.properties_frame, text="文件格式:").grid(row=row, column=0, sticky="w", pady=5)
            format_var = tk.StringVar(value=node.properties.get("format", "jpg"))
            format_combo = ttk.Combobox(self.properties_frame, textvariable=format_var, 
                                       values=["jpg", "png", "bmp"], state="readonly")
            format_combo.grid(row=row, column=1, sticky="ew", pady=5)
            format_combo.bind("<<ComboboxSelected>>", lambda e: self.update_node_property("format", format_var.get()))
            row += 1
            
            ttk.Label(self.properties_frame, text="覆盖现有文件:").grid(row=row, column=0, sticky="w", pady=5)
            overwrite_var = tk.BooleanVar(value=node.properties.get("overwrite", False))
            overwrite_check = ttk.Checkbutton(self.properties_frame, variable=overwrite_var)
            overwrite_check.grid(row=row, column=1, sticky="w", pady=5)
            overwrite_check.configure(command=lambda: self.update_node_property("overwrite", overwrite_var.get()))
            row += 1
        elif output_type == "database":
            ttk.Label(self.properties_frame, text="数据库名称:").grid(row=row, column=0, sticky="w", pady=5)
            db_name_var = tk.StringVar(value=node.properties.get("db_name", "face_db"))
            db_name_entry = ttk.Entry(self.properties_frame, textvariable=db_name_var, width=20)
            db_name_entry.grid(row=row, column=1, sticky="ew", pady=5)
            db_name_entry.bind("<FocusOut>", lambda e: self.update_node_property("db_name", db_name_var.get()))
            row += 1
            
            ttk.Label(self.properties_frame, text="集合名称:").grid(row=row, column=0, sticky="w", pady=5)
            collection_var = tk.StringVar(value=node.properties.get("collection", "faces"))
            collection_entry = ttk.Entry(self.properties_frame, textvariable=collection_var, width=20)
            collection_entry.grid(row=row, column=1, sticky="ew", pady=5)
            collection_entry.bind("<FocusOut>", lambda e: self.update_node_property("collection", collection_var.get()))
            row += 1
        elif output_type == "export":
            ttk.Label(self.properties_frame, text="导出格式:").grid(row=row, column=0, sticky="w", pady=5)
            export_format_var = tk.StringVar(value=node.properties.get("export_format", "json"))
            export_format_combo = ttk.Combobox(self.properties_frame, textvariable=export_format_var, 
                                              values=["json", "csv", "xml"], state="readonly")
            export_format_combo.grid(row=row, column=1, sticky="ew", pady=5)
            export_format_combo.bind("<<ComboboxSelected>>", lambda e: self.update_node_property("export_format", export_format_var.get()))
            row += 1
            
            ttk.Label(self.properties_frame, text="导出路径:").grid(row=row, column=0, sticky="w", pady=5)
            export_path_var = tk.StringVar(value=node.properties.get("export_path", "./exports"))
            export_path_entry = ttk.Entry(self.properties_frame, textvariable=export_path_var, width=30)
            export_path_entry.grid(row=row, column=1, sticky="ew", pady=5)
            export_path_entry.bind("<FocusOut>", lambda e: self.update_node_property("export_path", export_path_var.get()))
            row += 1
            
            ttk.Label(self.properties_frame, text="包含元数据:").grid(row=row, column=0, sticky="w", pady=5)
            include_meta_var = tk.BooleanVar(value=node.properties.get("include_metadata", True))
            include_meta_check = ttk.Checkbutton(self.properties_frame, variable=include_meta_var)
            include_meta_check.grid(row=row, column=1, sticky="w", pady=5)
            include_meta_check.configure(command=lambda: self.update_node_property("include_metadata", include_meta_var.get()))
        elif output_type == "message":
            # 消息输出节点属性
            ttk.Label(self.properties_frame, text="消息格式:").grid(row=row, column=0, sticky="w", pady=5)
            message_format_var = tk.StringVar(value=node.properties.get("message_format", "{result}"))
            message_format_entry = ttk.Entry(self.properties_frame, textvariable=message_format_var, width=30)
            message_format_entry.grid(row=row, column=1, sticky="ew", pady=5)
            message_format_entry.bind("<FocusOut>", lambda e: self.update_node_property("message_format", message_format_var.get()))
            row += 1
            
            ttk.Label(self.properties_frame, text="显示对话框:").grid(row=row, column=0, sticky="w", pady=5)
            show_dialog_var = tk.BooleanVar(value=node.properties.get("show_dialog", False))
            show_dialog_check = ttk.Checkbutton(self.properties_frame, variable=show_dialog_var)
            show_dialog_check.grid(row=row, column=1, sticky="w", pady=5)
            show_dialog_check.configure(command=lambda: self.update_node_property("show_dialog", show_dialog_var.get()))
            row += 1
            
            ttk.Label(self.properties_frame, text="日志到控制台:").grid(row=row, column=0, sticky="w", pady=5)
            log_to_console_var = tk.BooleanVar(value=node.properties.get("log_to_console", True))
            log_to_console_check = ttk.Checkbutton(self.properties_frame, variable=log_to_console_var)
            log_to_console_check.grid(row=row, column=1, sticky="w", pady=5)
            log_to_console_check.configure(command=lambda: self.update_node_property("log_to_console", log_to_console_var.get()))
    
    def _add_return_value_properties(self, node):
        """添加节点返回值相关属性"""
        # 获取属性区域的当前行数
        children = self.properties_frame.winfo_children()
        row = 0
        if children:
            # 查找最后一个控件的行号
            for widget in children:
                widget_row = widget.grid_info().get('row', 0)
                if widget_row > row:
                    row = widget_row
            row += 1  # 不添加额外间隔
        else:
            row = 2
        
        # 添加分割线和标题
        separator = ttk.Separator(self.properties_frame, orient="horizontal")
        separator.grid(row=row, column=0, columnspan=2, sticky="ew", pady=(0, 5))
        row += 1
        
        ttk.Label(self.properties_frame, text="返回值设置", font=('SimHei', 10, 'bold')).grid(row=row, column=0, columnspan=2, sticky="w", pady=5)
        row += 1
        
        # 默认返回值
        ttk.Label(self.properties_frame, text="默认返回值:").grid(row=row, column=0, sticky="w", pady=5)
        # 显示当前设置的默认返回值
        current_default_value = str(node.default_return_value) if node.default_return_value is not None else ""
        default_value_var = tk.StringVar(value=current_default_value)
        default_value_entry = ttk.Entry(self.properties_frame, textvariable=default_value_var, width=30)
        default_value_entry.grid(row=row, column=1, sticky="ew", pady=5)
        default_value_entry.bind("<FocusOut>", lambda e: self.update_node_default_value(default_value_var.get()))
        row += 1
        
        # 参数引用帮助提示
        help_text = "提示: 可使用 {upstream.字段名} 格式引用上游节点返回值，支持嵌套字段如 {upstream.field.subfield}"
        ttk.Label(self.properties_frame, text=help_text, foreground="#666666", font=('SimHei', 9)).grid(row=row, column=0, columnspan=2, sticky="w", pady=3)
        row += 1
        
        # 是否使用上游节点返回值
        ttk.Label(self.properties_frame, text="使用上游返回值:").grid(row=row, column=0, sticky="w", pady=5)
        use_upstream_var = tk.BooleanVar(value=node.properties.get("use_upstream_value", False))
        use_upstream_check = ttk.Checkbutton(self.properties_frame, variable=use_upstream_var)
        use_upstream_check.grid(row=row, column=1, sticky="w", pady=5)
        use_upstream_check.configure(command=lambda: self.update_node_property("use_upstream_value", use_upstream_var.get()))
        row += 1
        
        # 导入result_formatter模块
        try:
            from utils.result_formatter import result_formatter
        except ImportError:
            result_formatter = None
        
        # 当前节点返回值键值对显示
        if node.return_value is not None:
            # 添加分割线和标题
            separator = ttk.Separator(self.properties_frame, orient="horizontal")
            separator.grid(row=row, column=0, columnspan=2, sticky="ew", pady=10)
            row += 1
            
            ttk.Label(self.properties_frame, text="当前节点返回值", font=('SimHei', 10, 'bold')).grid(row=row, column=0, columnspan=2, sticky="w", pady=5)
            row += 1
            
            # 如果返回值是字典类型，以键值对形式显示
            if isinstance(node.return_value, dict):
                for key, value in node.return_value.items():
                    # 左侧显示key名称
                    ttk.Label(self.properties_frame, text=f"{key}:").grid(row=row, column=0, sticky="w", pady=3)
                    
                    # 右侧根据值的类型显示不同的组件
                    if isinstance(value, (str, int, float, bool, type(None))):
                        # 简单类型直接显示，支持文本换行
                        value_str = str(value) if value is not None else "None"
                        # 设置wraplength属性使长文本能够自动换行
                        ttk.Label(self.properties_frame, text=value_str, foreground="#333333", wraplength=300, justify="left").grid(row=row, column=1, sticky="w", pady=3)
                    else:
                        # 复杂类型显示提示并提供查看按钮
                        ttk.Label(self.properties_frame, text="(复杂数据)", foreground="#666666").grid(row=row, column=1, sticky="w", pady=3)
                    row += 1
                
                # 添加查看源JSON按钮
                view_json_btn = ttk.Button(self.properties_frame, text="查看源JSON", 
                                           command=lambda: self._show_json_dialog("当前节点返回值", node.return_value))
                view_json_btn.grid(row=row, column=0, columnspan=2, pady=5)
                row += 1
            else:
                # 非字典类型简单显示，支持文本换行
                ttk.Label(self.properties_frame, text="返回值:").grid(row=row, column=0, sticky="w", pady=3)
                # 设置wraplength属性使长文本能够自动换行
                ttk.Label(self.properties_frame, text=str(node.return_value), foreground="#333333", wraplength=300, justify="left").grid(row=row, column=1, sticky="w", pady=3)
                row += 1
        
        # 上游节点返回值键值对显示
        if node.get_upstream_nodes(self.canvas.current_flow):
            # 获取上游节点返回值
            upstream_value = node.get_upstream_return_value(self.canvas.current_flow)
            
            if upstream_value is not None:
                # 添加分割线和标题
                separator = ttk.Separator(self.properties_frame, orient="horizontal")
                separator.grid(row=row, column=0, columnspan=2, sticky="ew", pady=10)
                row += 1
                
                ttk.Label(self.properties_frame, text="上游节点返回值", font=('SimHei', 10, 'bold')).grid(row=row, column=0, columnspan=2, sticky="w", pady=5)
                row += 1
                
                # 如果返回值是字典类型，以键值对形式显示
                if isinstance(upstream_value, dict):
                    for key, value in upstream_value.items():
                        # 左侧显示key名称
                        ttk.Label(self.properties_frame, text=f"{key}:").grid(row=row, column=0, sticky="w", pady=3)
                        
                        # 右侧根据值的类型显示不同的组件
                        if isinstance(value, (str, int, float, bool, type(None))):
                            # 简单类型直接显示，支持文本换行
                            value_str = str(value) if value is not None else "None"
                            # 设置wraplength属性使长文本能够自动换行
                            ttk.Label(self.properties_frame, text=value_str, foreground="#333333", wraplength=300, justify="left").grid(row=row, column=1, sticky="w", pady=3)
                        else:
                            # 复杂类型显示提示并提供查看按钮
                            ttk.Label(self.properties_frame, text="(复杂数据)", foreground="#666666").grid(row=row, column=1, sticky="w", pady=3)
                        row += 1
                    
                    # 添加查看源JSON按钮
                    view_upstream_json_btn = ttk.Button(self.properties_frame, text="查看上游源JSON", 
                                                       command=lambda: self._show_json_dialog("上游节点返回值", upstream_value))
                    view_upstream_json_btn.grid(row=row, column=0, columnspan=2, pady=5)
                    row += 1
                else:
                    # 非字典类型简单显示，支持文本换行
                    ttk.Label(self.properties_frame, text="返回值:").grid(row=row, column=0, sticky="w", pady=3)
                    # 设置wraplength属性使长文本能够自动换行
                    ttk.Label(self.properties_frame, text=str(upstream_value), foreground="#333333", wraplength=300, justify="left").grid(row=row, column=1, sticky="w", pady=3)
                    row += 1
        
        # 上游节点链显示（只读）
        if node.get_upstream_nodes(self.canvas.current_flow):
            upstream_nodes = node.get_upstream_nodes(self.canvas.current_flow)
            upstream_names = ", ".join([n.name for n in upstream_nodes])
            ttk.Label(self.properties_frame, text="上游节点:").grid(row=row, column=0, sticky="w", pady=5)
            ttk.Label(self.properties_frame, text=upstream_names, foreground="#666666").grid(row=row, column=1, sticky="w", pady=5)
            row += 1

        # self.placeholder_label2 = ttk.Label(self.properties_frame, text=" ")
        # self.placeholder_label2.grid(row=99, column=0, pady=300)    
    def _show_json_dialog(self, title, data):
        """显示JSON源数据的对话框"""
        # 导入result_formatter模块
        try:
            from utils.result_formatter import result_formatter
        except ImportError:
            result_formatter = None
        
        # 创建对话框
        dialog = tk.Toplevel(self)
        dialog.title(f"{title} - 源JSON数据")
        dialog.geometry("800x600")
        dialog.minsize(600, 400)
        
        # 添加滚动条
        scrollbar = ttk.Scrollbar(dialog)
        scrollbar.pack(side="right", fill="y")
        
        # 添加文本框
        text_widget = tk.Text(dialog, wrap="word", yscrollcommand=scrollbar.set)
        text_widget.pack(side="left", fill="both", expand=True, padx=10, pady=10)
        
        # 配置滚动条
        scrollbar.config(command=text_widget.yview)
        
        # 格式化JSON数据
        if result_formatter and isinstance(data, dict):
            json_text = result_formatter.format_result_as_json(data)
        else:
            # 尝试手动格式化
            try:
                import json
                json_text = json.dumps(data, ensure_ascii=False, indent=2)
            except:
                json_text = str(data)
        
        # 插入JSON数据
        text_widget.insert("1.0", json_text)
        text_widget.configure(state="disabled")  # 设置为只读
        
        # 添加复制按钮
        def copy_to_clipboard():
            dialog.clipboard_clear()
            dialog.clipboard_append(json_text)
            dialog.update()  # 保持剪贴板内容
        
        copy_btn = ttk.Button(dialog, text="复制JSON", command=copy_to_clipboard)
        copy_btn.pack(pady=10)
        
        # 确保对话框在最上层
        dialog.attributes('-topmost', True)
        dialog.attributes('-topmost', False)  # 然后关闭最上层，让用户可以切换窗口
        
        # 设置对话框模态
        dialog.transient(self)  # 设置为主窗口的子窗口
        dialog.grab_set()  # 模态对话框
        self.wait_window(dialog)  # 等待对话框关闭
        

        
    def update_node_name(self, name):
        """更新节点名称"""
        if self.current_node_id and self.canvas.current_flow:
            node = self.canvas.current_flow.nodes.get(self.current_node_id)
            if node:
                node.name = name
                self.canvas.redraw_flow()
                self.title_label.config(text=f"节点属性: {name}")
                
    def update_node_type(self, node_type):
        """更新节点类型"""
        if self.current_node_id and self.canvas.current_flow:
            node = self.canvas.current_flow.nodes.get(self.current_node_id)
            if node:
                node.node_type = node_type
                # 清空节点属性，防止属性冲突
                node.properties.clear()
                # 根据新类型设置默认属性
                if node_type == "input":
                    node.properties["input_type"] = "image"
                elif node_type == "process":
                    node.properties["process_type"] = "face_detection"
                elif node_type == "output":
                    node.properties["output_type"] = "display"
                
                self.canvas.redraw_flow()
                self.update_properties()
                
    def update_node_default_value(self, default_value_str):
        """更新节点的默认返回值"""
        if self.current_node_id and self.canvas.current_flow:
            node = self.canvas.current_flow.nodes.get(self.current_node_id)
            if node:
                # 尝试解析值的类型
                if not default_value_str:
                    node.default_return_value = None
                elif default_value_str.lower() == "true":
                    node.default_return_value = True
                elif default_value_str.lower() == "false":
                    node.default_return_value = False
                elif default_value_str.isdigit():
                    node.default_return_value = int(default_value_str)
                elif '.' in default_value_str and all(part.isdigit() for part in default_value_str.split('.') if part):
                    node.default_return_value = float(default_value_str)
                elif default_value_str.startswith('{') and default_value_str.endswith('}'):
                    try:
                        node.default_return_value = json.loads(default_value_str)
                    except json.JSONDecodeError:
                        node.default_return_value = default_value_str
                elif default_value_str.startswith('[') and default_value_str.endswith(']'):
                    try:
                        node.default_return_value = json.loads(default_value_str)
                    except json.JSONDecodeError:
                        node.default_return_value = default_value_str
                else:
                    node.default_return_value = default_value_str
                self.canvas.redraw_flow()
                
    def update_node_property(self, property_name, property_value):
        """更新节点属性"""
        if self.current_node_id and self.canvas.current_flow:
            node = self.canvas.current_flow.nodes.get(self.current_node_id)
            if node:
                node.properties[property_name] = property_value


class ApplicationListPanel(ttk.Frame):
    """应用列表面板，用于显示和管理应用列表"""
    def __init__(self, parent, flow_manager, on_app_select, **kwargs):
        super().__init__(parent, **kwargs)
        self.parent = parent
        self.flow_manager = flow_manager
        self.on_app_select = on_app_select
        
        # 设置样式
        self.configure(padding="10")
        
        # 创建标题
        ttk.Label(self, text="应用列表", font=("Arial", 12, "bold")).pack(pady=(0, 10))
        
        # 创建应用列表
        self.app_listbox = tk.Listbox(self, width=30, height=15)
        self.app_listbox.pack(fill="both", expand=True, pady=(0, 10))
        self.app_listbox.bind("<<ListboxSelect>>", self.on_list_select)
        
        # 创建按钮区域
        button_frame = ttk.Frame(self)
        button_frame.pack(fill="x")
        
        ttk.Button(button_frame, text="新建应用", command=self.on_new_app).pack(side="left", padx=5, pady=5)
        ttk.Button(button_frame, text="删除应用", command=self.on_delete_app).pack(side="left", padx=5, pady=5)
        ttk.Button(button_frame, text="刷新列表", command=self.refresh_list).pack(side="left", padx=5, pady=5)
        
        # 刷新列表
        self.refresh_list()
        
    def refresh_list(self):
        """刷新应用列表"""
        self.app_listbox.delete(0, tk.END)
        for app_id, app in self.flow_manager.applications.items():
            self.app_listbox.insert(tk.END, f"{app.app_name} ({app_id})")
            
    def on_list_select(self, event):
        """处理列表选择事件"""
        selection = self.app_listbox.curselection()
        if selection:
            app_info = self.app_listbox.get(selection[0])
            app_id = app_info.split(" (")[-1][:-1]  # 提取应用ID
            if app_id in self.flow_manager.applications:
                self.on_app_select(self.flow_manager.applications[app_id])
                
    def on_new_app(self):
        """创建新应用"""
        # 弹出对话框，输入应用名称
        from tkinter.simpledialog import askstring
        app_name = askstring("新建应用", "请输入应用名称:")
        if app_name:
            # 创建新应用
            app = self.flow_manager.create_application(app_name)
            
            # 刷新列表并选中新应用
            self.refresh_list()
            # 找到新应用的索引
            for i in range(self.app_listbox.size()):
                if app.app_id in self.app_listbox.get(i):
                    self.app_listbox.selection_set(i)
                    self.app_listbox.see(i)
                    self.on_list_select(None)
                    break
                    
    def on_delete_app(self):
        """删除应用"""
        selection = self.app_listbox.curselection()
        if selection:
            app_info = self.app_listbox.get(selection[0])
            app_id = app_info.split(" (")[-1][:-1]  # 提取应用ID
            
            # 确认删除
            if messagebox.askyesno("确认删除", f"确定要删除应用 {app_info.split(' (')[0]} 吗？"):
                # 删除应用
                if self.flow_manager.delete_application(app_id):
                    # 刷新列表
                    self.refresh_list()
                    # 清除选中状态
                    self.on_app_select(None)
                    messagebox.showinfo("删除成功", "应用已成功删除！")
                else:
                    messagebox.showerror("删除失败", "删除应用失败！")


def create_app_library_tab(tab_control: ttk.Notebook, parent_gui) -> ttk.Frame:
    """
    创建应用库选项卡
    :param tab_control: 选项卡控制器
    :param parent_gui: 父窗口GUI实例
    :return: 创建好的选项卡框架
    """
    # 创建选项卡框架
    app_tab = ttk.Frame(tab_control)
    
    # 添加选项卡到控制器
    tab_control.add(app_tab, text="应用库")
    
    # 配置选项卡的网格权重，使内容可以撑满整个选项卡
    app_tab.columnconfigure(0, weight=0)
    app_tab.columnconfigure(1, weight=1)
    app_tab.columnconfigure(2, weight=0)
    app_tab.rowconfigure(0, weight=0)
    app_tab.rowconfigure(1, weight=1)
    
    # 创建流程管理器
    flow_manager = FlowManager()
    
    # 创建左侧应用列表面板
    app_list_panel = ApplicationListPanel(app_tab, flow_manager, lambda app: canvas.set_flow(app))
    app_list_panel.grid(row=1, column=0, sticky="nsew", padx=(5, 5), pady=5)
    
    # 创建顶部按钮区域
    top_frame = ttk.Frame(app_tab)
    top_frame.grid(row=0, column=0, columnspan=3, sticky="ew", padx=5, pady=5)
    top_frame.columnconfigure(0, weight=1)
    
    # 创建顶部按钮
    ttk.Button(top_frame, text="测试应用", command=lambda: test_application(canvas)).pack(side="right", padx=5, pady=5)
    ttk.Button(top_frame, text="保存流程", command=lambda: save_current_flow(flow_manager, canvas)).pack(side="right", padx=5, pady=5)
    ttk.Button(top_frame, text="删除选中节点", command=lambda: canvas.delete_selected_node()).pack(side="right", padx=5, pady=5)
    ttk.Button(top_frame, text="删除选中连接线", command=lambda: canvas.delete_selected_connection()).pack(side="right", padx=5, pady=5)
    ttk.Button(top_frame, text="清空画布", command=lambda: clear_canvas(canvas)).pack(side="right", padx=5, pady=5)
    
    # 创建中间流程画布区域
    canvas_frame = ttk.LabelFrame(app_tab, text="应用流程编辑", padding="5")
    canvas_frame.grid(row=1, column=1, sticky="nsew", padx=(0, 5), pady=5)
    canvas_frame.columnconfigure(0, weight=1)
    canvas_frame.rowconfigure(0, weight=1)
    
    # 创建画布滚动条
    canvas_scroll_x = ttk.Scrollbar(canvas_frame, orient="horizontal")
    canvas_scroll_y = ttk.Scrollbar(canvas_frame, orient="vertical")
    canvas_scroll_x.grid(row=1, column=0, sticky="ew")
    canvas_scroll_y.grid(row=0, column=1, sticky="ns")
    
    # 创建流程画布
    canvas = FlowCanvas(canvas_frame, flow_manager, 
                       xscrollcommand=canvas_scroll_x.set, 
                       yscrollcommand=canvas_scroll_y.set, 
                       width=600, height=400)
    canvas.grid(row=0, column=0, sticky="nsew")
    canvas_scroll_x.configure(command=canvas.xview)
    canvas_scroll_y.configure(command=canvas.yview)
    
    # 创建右侧工具栏区域
    toolbar_frame = ttk.Frame(app_tab)
    toolbar_frame.grid(row=1, column=2, sticky="nsew", padx=(0, 5), pady=5)
    # 设置固定宽度，确保右侧面板大小稳定
    toolbar_frame.columnconfigure(0, weight=1, minsize=300)
    
    # 创建节点调色板
    node_palette = NodePalette(toolbar_frame, canvas)
    node_palette.grid(row=0, column=0, sticky="nsew", pady=(0, 10))
    
    # 创建节点属性编辑器
    node_props_editor = NodePropertiesEditor(toolbar_frame, canvas)
    node_props_editor.grid(row=1, column=0, sticky="nsew")
    
    # 设置画布右键菜单
    def setup_canvas_context_menu():
        # 创建右键菜单
        context_menu = tk.Menu(canvas, tearoff=0)
        
        # 添加菜单项
        context_menu.add_command(label="删除选中节点", command=canvas.delete_selected_node)
        context_menu.add_command(label="删除选中连接线", command=canvas.delete_selected_connection)
        context_menu.add_separator()
        context_menu.add_command(label="清空画布", command=lambda: clear_canvas(canvas))
        
        # 绑定右键菜单事件
        def show_context_menu(event):
            canvas.focus_set()  # 获取焦点
            context_menu.post(event.x_root, event.y_root)
        
        canvas.bind("<Button-3>", show_context_menu)
    
    # 设置右键菜单
    setup_canvas_context_menu()
    
    # 将canvas保存到parent_gui中，方便后续访问
    parent_gui.app_canvas = canvas
    
    return app_tab


def save_current_flow(flow_manager, canvas):
    """保存当前流程"""
    if canvas.current_flow:
        import datetime
        canvas.current_flow.updated_at = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        if flow_manager.save_application(canvas.current_flow):
            messagebox.showinfo("保存成功", "应用流程已成功保存！")
        else:
            messagebox.showerror("保存失败", "保存应用流程失败！")
    else:
        messagebox.showwarning("警告", "请先选择或创建一个应用！")


def clear_canvas(canvas):
    """清空画布"""
    if canvas.current_flow and messagebox.askyesno("确认清空", "确定要清空当前画布吗？"):
        canvas.current_flow.nodes = {}
        canvas.redraw_flow()


def test_application(canvas):
    """测试应用流程 - 实现真正的应用执行功能"""
    if not canvas.current_flow:
        messagebox.showwarning("警告", "请先选择或创建一个应用！")
        return
        
    # 检查应用是否包含有效的流程
    if not canvas.current_flow.nodes:
        messagebox.showwarning("警告", "应用流程为空，请先添加节点！")
        return
        
    # 创建测试对话框
    test_window = tk.Toplevel(canvas.master)
    test_window.title(f"测试应用：{canvas.current_flow.app_name}")
    test_window.geometry("700x500")
    
    # 创建结果显示区域
    result_text = scrolledtext.ScrolledText(test_window, wrap=tk.WORD, width=80, height=25)
    result_text.pack(fill="both", expand=True, padx=10, pady=10)
    
    def append_log(text):
        """向日志区域添加文本"""
        result_text.config(state="normal")
        result_text.insert(tk.END, text)
        result_text.see(tk.END)  # 滚动到底部
        result_text.config(state="disabled")
        test_window.update()  # 立即更新显示
    
    # 添加必要的导入
    import os
    import cv2
    import numpy as np
    import json
    from tkinter import filedialog
    
    # 尝试导入人脸处理相关模块
    try:
        from modules.face_detection import detect_faces
        from modules.face_features import extract_face_features
        from modules.face_matching import recognize_face
        from modules.face_database import list_faces_in_database
        face_modules_available = True
        append_log("人脸处理模块加载成功！\n")
    except Exception as e:
        face_modules_available = False
        append_log(f"警告：无法加载人脸处理模块：{e}\n")
    
    # 添加测试日志
    append_log(f"开始测试应用：{canvas.current_flow.app_name}\n")
    append_log(f"应用ID：{canvas.current_flow.app_id}\n")
    append_log(f"包含节点数：{len(canvas.current_flow.nodes)}\n\n")
    
    # 重置所有节点的返回值
    for node in canvas.current_flow.nodes.values():
        node.return_value = None
    
    # 查找所有输出节点，这些是执行流程的起点
    output_nodes = [node for node in canvas.current_flow.nodes.values() if node.node_type == "output"]
    
    if not output_nodes:
        append_log("警告：应用缺少输出节点！\n")
    
    # 执行流程
    append_log("开始执行流程...\n")
    for output_node in output_nodes:
        append_log(f"\n从输出节点 '{output_node.name}' 开始追溯执行流程：\n")
        
        # 获取完整的节点链（从输入到输出的顺序）
        node_chain = canvas.current_flow.get_node_chain(output_node.node_id)
        append_log(f"执行顺序：{', '.join([n.name for n in node_chain])}\n\n")
        
        # 执行每个节点
        for node in node_chain:
            append_log(f"执行节点：{node.name} (类型: {node.node_type})\n")
            
            # 检查是否使用上游节点的返回值
            use_upstream = node.properties.get("use_upstream_value", False)
            
            # 获取上游节点的返回值
            upstream_value = None
            if use_upstream:
                upstream_value = node.get_upstream_return_value(canvas.current_flow)
                if upstream_value is not None:
                    append_log(f"  使用上游节点返回值: {upstream_value}\n")
            
            # 执行节点实际功能
            if node.node_type == "input":
                # 输入节点 - 实现真正的图像加载
                input_type = node.properties.get("input_type", "image")
                if input_type == "image":
                    # 实际从文件选择器获取图像路径
                    image_path = node.properties.get("image_path", "")
                    if not image_path or not os.path.exists(image_path):
                        # 如果没有提供有效路径，显示文件选择对话框
                        file_types = [("图像文件", "*.jpg;*.jpeg;*.png;*.bmp")]
                        image_path = filedialog.askopenfilename(title="选择图像文件", filetypes=file_types)
                    
                    if image_path and os.path.exists(image_path):
                        append_log(f"  加载图像: {image_path}\n")
                        # 读取图像信息
                        img = cv2.imread(image_path)
                        height, width = img.shape[:2]
                        node.return_value = {"type": "image", "path": image_path, "size": f"{width}x{height}"}
                    else:
                        append_log(f"  警告: 无法加载图像\n")
                        node.return_value = {"type": "image", "error": "无法加载图像"}
                elif input_type == "video":
                    # 视频节点
                    video_path = node.properties.get("video_path", "")
                    if not video_path or not os.path.exists(video_path):
                        append_log(f"  警告: 视频文件不存在\n")
                        node.return_value = {"type": "video", "error": "视频文件不存在"}
                    else:
                        append_log(f"  加载视频: {video_path}\n")
                        # 尝试打开视频获取信息
                        cap = cv2.VideoCapture(video_path)
                        fps = cap.get(cv2.CAP_PROP_FPS)
                        width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
                        height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
                        cap.release()
                        node.return_value = {"type": "video", "path": video_path, "fps": fps, "size": f"{width}x{height}"}
                elif input_type == "camera":
                    # 摄像头节点 - 添加拍照功能
                    camera_id = node.properties.get("camera_id", 0)
                    append_log(f"  启动摄像头: {camera_id}\n")
                    
                    # 创建临时文件路径用于保存拍摄的照片
                    import os
                    # 获取data目录的绝对路径
                    data_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "data")
                    # 确保数据目录存在
                    os.makedirs(data_dir, exist_ok=True)
                    
                    # 获取当前应用ID，如果可用
                    app_id = "default"  # 默认值
                    if hasattr(canvas, 'current_flow') and hasattr(canvas.current_flow, 'app_id'):
                        app_id = canvas.current_flow.app_id
                        
                    # 创建应用专属临时文件夹
                    app_temp_dir = os.path.join(data_dir, "temp", app_id)
                    os.makedirs(app_temp_dir, exist_ok=True)
                    
                    # 根据操作系统类型处理中文路径问题
                    if os.name == 'nt':  # Windows系统
                        # 使用纯英文临时文件路径避免中文乱码问题
                        temp_photo_path = os.path.join(app_temp_dir, "temp_photo.jpg")
                    else:
                        # 非Windows系统使用正常路径
                        temp_photo_path = os.path.join(app_temp_dir, "temp_photo.jpg")
                    
                    # 创建摄像头窗口并等待用户拍照
                    try:
                        cap = cv2.VideoCapture(camera_id)
                        
                        # 检查摄像头是否成功打开
                        if not cap.isOpened():
                            append_log(f"  错误: 无法打开摄像头 {camera_id}\n")
                            node.return_value = {"type": "camera", "error": f"无法打开摄像头 {camera_id}"}
                        else:
                            append_log(f"  摄像头已打开，请按空格键拍照，按ESC键取消\n")
                            
                            # 创建窗口用于显示摄像头画面
                            cv2.namedWindow("Camera Preview", cv2.WINDOW_NORMAL)
                            photo_taken = False
                            
                            while True:
                                # 读取一帧
                                ret, frame = cap.read()
                                if not ret:
                                    append_log(f"  错误: 无法读取摄像头数据\n")
                                    break
                                
                                # 在画面上显示提示信息 - 使用PIL处理中文显示
                                from PIL import Image, ImageDraw, ImageFont
                                import numpy as np
                                
                                # 将OpenCV图像转换为PIL图像
                                pil_img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
                                draw = ImageDraw.Draw(pil_img)
                                
                                # 尝试加载支持中文的字体
                                try:
                                    # 使用Windows系统字体
                                    font = ImageFont.truetype("simhei.ttf", 20)
                                except:
                                    try:
                                        font = ImageFont.truetype("msyh.ttc", 20)
                                    except:
                                        # 如果无法加载字体，使用默认字体
                                        font = ImageFont.load_default()
                                
                                # 在PIL图像上绘制中文
                                draw.text((10, 10), "按空格键拍照，按ESC键取消", font=font, fill=(0, 255, 0))
                                
                                # 将PIL图像转换回OpenCV图像
                                frame = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
                                
                                # 显示画面
                                cv2.imshow("Camera Preview", frame)
                                
                                # 等待按键
                                key = cv2.waitKey(1) & 0xFF
                                
                                # 按空格键拍照
                                if key == 32:  # 空格键
                                    # 保存当前帧 - 使用PIL处理中文路径问题
                                    from PIL import Image
                                    import numpy as np
                                    
                                    # 将OpenCV的BGR格式转换为PIL的RGB格式
                                    rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                                    
                                    # 使用PIL保存图像
                                    try:
                                        pil_image = Image.fromarray(rgb_frame)
                                        pil_image.save(temp_photo_path)
                                        photo_taken = True
                                        append_log(f"  照片已拍摄并保存到: {temp_photo_path}\n")
                                        break
                                    except Exception as e:
                                        append_log(f"  保存照片失败: {e}\n")
                                        # 如果PIL保存失败，尝试使用OpenCV的备用方法
                                        try:
                                            # 使用OpenCV的imencode和tofile方法绕过中文路径问题
                                            success, encoded_image = cv2.imencode('.jpg', frame)
                                            if success:
                                                encoded_image.tofile(temp_photo_path)
                                                photo_taken = True
                                                append_log(f"  照片已使用备用方法保存到: {temp_photo_path}\n")
                                                break
                                        except Exception as e2:
                                            append_log(f"  备用保存方法也失败: {e2}\n")
                                
                                # 按ESC键取消
                                elif key == 27:  # ESC键
                                    append_log(f"  拍照已取消\n")
                                    break
                            
                            # 释放摄像头和关闭窗口
                            cap.release()
                            # 使用与创建窗口时相同的英文标题
                            cv2.destroyWindow("Camera Preview")
                            
                            # 如果拍摄了照片，设置返回值
                            if photo_taken:
                                # 检查文件是否存在
                                if not os.path.exists(temp_photo_path):
                                    append_log(f"  错误: 照片文件不存在\n")
                                    node.return_value = {"type": "camera", "error": "照片文件不存在"}
                                else:
                                    try:
                                        # 使用PIL读取图像以处理中文路径
                                        from PIL import Image
                                        import numpy as np
                                        
                                        # 使用PIL读取图像
                                        pil_image = Image.open(temp_photo_path)
                                        width, height = pil_image.size
                                        
                                        # 可选：转换为OpenCV格式供后续处理使用
                                        img = np.array(pil_image)
                                        if img.ndim == 3:
                                            img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
                                        
                                        node.return_value = {"type": "image", "path": temp_photo_path, "size": f"{width}x{height}"}
                                    except Exception as e:
                                        append_log(f"  错误: 无法读取拍摄的照片: {e}\n")
                                        # 如果PIL读取失败，尝试使用OpenCV的备用方法
                                        try:
                                            # 使用OpenCV的imdecode和fromfile方法绕过中文路径问题
                                            img_data = np.fromfile(temp_photo_path, dtype=np.uint8)
                                            img = cv2.imdecode(img_data, cv2.IMREAD_COLOR)
                                            if img is not None:
                                                height, width = img.shape[:2]
                                                node.return_value = {"type": "image", "path": temp_photo_path, "size": f"{width}x{height}"}
                                            else:
                                                append_log(f"  错误: 备用读取方法也失败\n")
                                                node.return_value = {"type": "camera", "error": "无法读取拍摄的照片"}
                                        except Exception as e2:
                                            append_log(f"  备用读取方法也失败: {e2}\n")
                                            node.return_value = {"type": "camera", "error": "无法读取拍摄的照片"}
                            else:
                                node.return_value = {"type": "camera", "error": "未拍摄照片"}
                    except Exception as e:
                        append_log(f"  摄像头操作异常: {e}\n")
                        # 即使发生异常，如果照片已拍摄，仍然返回有效的图像路径
                        if photo_taken and os.path.exists(temp_photo_path):
                            append_log(f"  注意：虽然发生异常，但照片已成功保存\n")
                            node.return_value = {"type": "image", "path": temp_photo_path}
                        else:
                            node.return_value = {"type": "camera", "error": str(e)}
            
            elif node.node_type == "process" and face_modules_available:
                # 处理节点 - 实现真正的人脸处理功能
                process_type = node.properties.get("process_type", "face_detection")
                
                # 获取上游图像数据
                image_path = None
                if upstream_value and isinstance(upstream_value, dict) and "path" in upstream_value:
                    image_path = upstream_value["path"]
                
                if process_type == "face_detection":
                    # 实现真正的人脸检测
                    append_log("  执行人脸检测\n")
                    if image_path and os.path.exists(image_path):
                        try:
                            face_locations = detect_faces(image_path)
                            face_count = len(face_locations)
                            append_log(f"  检测到 {face_count} 个人脸\n")
                            
                            # 绘制检测结果并显示
                            image = cv2.imread(image_path)
                            for (top, right, bottom, left) in face_locations:
                                cv2.rectangle(image, (left, top), (right, bottom), (0, 0, 255), 2)
                            
                            # 显示结果
                            cv2.imshow("Face Detection Result", image)
                            cv2.waitKey(0)
                            cv2.destroyWindow("Face Detection Result")
                            
                            node.return_value = {"type": "face_detection_result", "face_count": face_count, "face_locations": face_locations}
                        except Exception as e:
                            append_log(f"  人脸检测失败: {e}\n")
                            node.return_value = {"type": "face_detection_result", "error": str(e)}
                    else:
                        append_log("  错误: 没有有效的图像路径\n")
                        node.return_value = {"type": "face_detection_result", "error": "没有有效的图像路径"}
                
                elif process_type == "face_recognition":
                    # 获取静默模式选项
                    silent_mode = node.properties.get("silent_mode", False)
                    
                    # 获取当前应用ID，如果可用
                    app_id = "default"  # 默认值
                    if hasattr(canvas, 'current_flow') and hasattr(canvas.current_flow, 'app_id'):
                        app_id = canvas.current_flow.app_id
                        
                    # 创建应用专属临时文件夹
                    app_temp_dir = os.path.join("data", "temp", app_id)
                    os.makedirs(app_temp_dir, exist_ok=True)
                    
                    # 实现真正的人脸识别
                    if not silent_mode:
                        append_log("  执行人脸识别\n")
                    if image_path and os.path.exists(image_path):
                        try:
                            # 首先检测人脸
                            face_locations = detect_faces(image_path)
                            if not face_locations:
                                if not silent_mode:
                                    append_log("  未检测到人脸\n")
                                node.return_value = {"type": "face_recognition_result", "recognized_faces": [], "message": "未检测到人脸"}
                            else:
                                # 识别每个人脸
                                recognized_faces = []
                                try:
                                    # 确保正确导入所需模块
                                    from modules.face_features import extract_face_features
                                    from utils.data_storage import load_face_database
                                    from modules.face_matching import recognize_face
                                    
                                    # 先提取人脸特征
                                    face_encodings = extract_face_features(image_path)
                                    
                                    # 加载人脸数据库 - 使用默认数据库文件路径
                                    database_file = "data/face_database.json"
                                    face_database = load_face_database(database_file)
                                    
                                    for i, face_encoding in enumerate(face_encodings):
                                        name, confidence = recognize_face(face_encoding, face_database)
                                        recognized_faces.append({"name": name, "confidence": confidence})
                                        if not silent_mode:
                                            append_log(f"  识别结果 #{i+1}: {name} (置信度: {confidence:.2f})\n")
                                except Exception as e:
                                    if not silent_mode:
                                        append_log(f"  人脸特征提取或识别失败: {str(e)}\n")
                                    # 即使识别失败也记录为未知
                                    for i in range(len(face_locations)):
                                        recognized_faces.append({"name": "未知", "confidence": 0})
                                        if not silent_mode:
                                            append_log(f"  识别结果 #{i+1}: 未知 (置信度: 0.00)\n")
                                
                                # 绘制识别结果 - 规范化路径以解决Windows路径问题
                                normalized_path = os.path.normpath(image_path)
                                image = cv2.imread(normalized_path)
                                if image is not None:
                                    # 确保face_locations和recognized_faces长度匹配
                                    min_length = min(len(face_locations), len(recognized_faces))
                                    for i in range(min_length):
                                        top, right, bottom, left = face_locations[i]
                                        # 画框
                                        cv2.rectangle(image, (left, top), (right, bottom), (0, 255, 0), 2)
                                        # 显示名字
                                        name = recognized_faces[i]["name"]
                                        confidence = recognized_faces[i]["confidence"]
                                        label = f"{name} ({confidence:.2f})"
                                        cv2.putText(image, label, (left, top - 10), 
                                                    cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
                                    
                                    # 根据静默模式决定是否显示结果窗口
                                    if not silent_mode:
                                        # 使用中文标题
                                        cv2.imshow("人脸识别结果", image)
                                        cv2.waitKey(0)
                                        cv2.destroyWindow("人脸识别结果")
                                    
                                    node.return_value = {"type": "face_recognition_result", "recognized_faces": recognized_faces}
                                else:
                                    if not silent_mode:
                                        append_log(f"  错误: 无法读取图像文件 {image_path}\n")
                                    node.return_value = {"type": "face_recognition_result", "error": f"无法读取图像文件 {image_path}"}
                        except Exception as e:
                            append_log(f"  人脸识别失败: {e}\n")
                            node.return_value = {"type": "face_recognition_result", "error": str(e)}
                    else:
                        append_log("  错误: 没有有效的图像路径\n")
                        node.return_value = {"type": "face_recognition_result", "error": "没有有效的图像路径"}
                
                elif process_type == "feature_extraction":
                    # 实现真正的特征提取
                    append_log("  提取人脸特征\n")
                    if image_path and os.path.exists(image_path):
                        try:
                            # 首先检测人脸
                            face_locations = detect_faces(image_path)
                            if not face_locations:
                                append_log("  未检测到人脸\n")
                                node.return_value = {"type": "features", "feature_vectors": [], "message": "未检测到人脸"}
                            else:
                                # 提取特征
                                face_encodings = extract_face_features(image_path, face_locations)
                                feature_vectors = [encoding.tolist() for encoding in face_encodings]
                                append_log(f"  提取了 {len(feature_vectors)} 个人脸特征\n")
                                node.return_value = {"type": "features", "feature_vectors": feature_vectors, "dimensions": 128}
                        except Exception as e:
                            append_log(f"  特征提取失败: {e}\n")
                            node.return_value = {"type": "features", "error": str(e)}
                    else:
                        append_log("  错误: 没有有效的图像路径\n")
                        node.return_value = {"type": "features", "error": "没有有效的图像路径"}
                
                elif process_type == "api_call":
                    # API调用节点
                    api_url = node.properties.get("api_url", "https://api.example.com/face")
                    api_key = node.properties.get("api_key", "sample_api_key")
                    http_method = node.properties.get("http_method", "GET")
                    headers_str = node.properties.get("headers", '{"Content-Type": "application/json"}')
                    body_str = node.properties.get("body", "{}")
                    append_log(f"  调用API: {api_url}\n")
                    append_log(f"  方法: {http_method}\n")
                    
                    # 处理参数中的上游数据引用 {upstream.field}
                    def resolve_upstream_refs(text, upstream_value):
                        if not text or not upstream_value:
                            return text
                        
                        import re
                        # 查找所有 {upstream.字段路径} 格式的引用
                        pattern = r'\{upstream\.([^}]+)\}'
                        matches = re.findall(pattern, text)
                        
                        for match in matches:
                            # 尝试从上游值中获取对应字段
                            value = upstream_value
                            try:
                                # 支持嵌套字段访问，如 {upstream.field.subfield}
                                for part in match.split('.'):
                                    if isinstance(value, dict):
                                        value = value.get(part)
                                    elif isinstance(value, (list, tuple)) and part.isdigit():
                                        idx = int(part)
                                        if idx < len(value):
                                            value = value[idx]
                                    else:
                                        value = None
                                        break
                                
                                if value is not None:
                                    # 替换引用为实际值
                                    ref_str = f"{{upstream.{match}}}"
                                    if isinstance(value, (dict, list)):
                                        # 如果是复杂类型，转换为JSON字符串
                                        text = text.replace(ref_str, json.dumps(value, ensure_ascii=False))
                                    else:
                                        # 基本类型直接替换
                                        text = text.replace(ref_str, str(value))
                                else:
                                    append_log(f"  警告: 无法解析上游引用 {{upstream.{match}}}\n")
                            except Exception as e:
                                append_log(f"  解析上游引用失败: {e}\n")
                        
                        return text
                    
                    # 解析API参数中的上游数据引用
                    api_url = resolve_upstream_refs(api_url, upstream_value)
                    api_key = resolve_upstream_refs(api_key, upstream_value)
                    headers_str = resolve_upstream_refs(headers_str, upstream_value)
                    body_str = resolve_upstream_refs(body_str, upstream_value)
                    
                    try:
                        # 尝试解析headers
                        headers = json.loads(headers_str)
                        # 添加API Key（如果存在）
                        if api_key:
                            headers["Authorization"] = f"Bearer {api_key}"
                        
                        # 尝试解析body
                        body = json.loads(body_str) if body_str else None
                        
                        # 实际执行API调用
                        import requests
                        if http_method == "GET":
                            response = requests.get(api_url, headers=headers, params=body)
                        elif http_method == "POST":
                            response = requests.post(api_url, headers=headers, json=body)
                        elif http_method == "PUT":
                            response = requests.put(api_url, headers=headers, json=body)
                        elif http_method == "DELETE":
                            response = requests.delete(api_url, headers=headers, json=body)
                        else:
                            raise ValueError(f"不支持的HTTP方法: {http_method}")
                        
                        # 处理响应
                        if response.status_code == 200:
                            node.return_value = {"type": "api_response", "url": api_url, "status": "success", "status_code": response.status_code, "data": response.json()}
                            append_log(f"  API调用成功，状态码: {response.status_code}\n")
                        else:
                            node.return_value = {"type": "api_response", "url": api_url, "status": "error", "status_code": response.status_code, "error": response.text}
                            append_log(f"  API调用失败，状态码: {response.status_code}\n")
                    except Exception as e:
                        # 处理异常，使用模拟数据
                        node.return_value = {"type": "api_response", "url": api_url, "status": "error", "error": str(e)}
                        append_log(f"  API调用异常: {e}\n")
                        append_log(f"  使用模拟数据作为返回值\n")
                else:
                    # 其他处理类型，保持原有模拟行为
                    node.return_value = {"type": "process_result", "status": "success", "message": "处理完成"}
            
            elif node.node_type == "process" and not face_modules_available:
                # 人脸模块不可用时，使用模拟行为
                append_log("  警告: 人脸处理模块不可用，使用模拟结果\n")
                process_type = node.properties.get("process_type", "face_detection")
                if process_type == "face_detection":
                    node.return_value = {"type": "face_detection_result", "face_count": 2, "face_locations": [(100, 200, 300, 100), (400, 500, 600, 400)]}
                elif process_type == "face_recognition":
                    node.return_value = {"type": "face_recognition_result", "recognized_faces": [{"name": "张三", "confidence": 0.95}]}
                elif process_type == "feature_extraction":
                    node.return_value = {"type": "features", "feature_vectors": [[0.1] * 128], "dimensions": 128}
                else:
                    node.return_value = {"type": "process_result", "status": "success"}
            
            elif node.node_type == "output":
                # 输出节点 - 实现真正的结果输出
                output_type = node.properties.get("output_type", "display")
                
                # 处理参数中的上游数据引用 {upstream.field}
                def resolve_upstream_refs(text, upstream_value):
                    if not text or not upstream_value:
                        return text
                    
                    import re
                    # 查找所有 {upstream.字段路径} 格式的引用
                    pattern = r'\{upstream\.([^}]+)\}'
                    matches = re.findall(pattern, text)
                    
                    for match in matches:
                        # 尝试从上游值中获取对应字段
                        value = upstream_value
                        try:
                            # 支持嵌套字段访问，如 {upstream.field.subfield}
                            for part in match.split('.'):
                                if isinstance(value, dict):
                                    value = value.get(part)
                                elif isinstance(value, (list, tuple)) and part.isdigit():
                                    idx = int(part)
                                    if idx < len(value):
                                        value = value[idx]
                                else:
                                    value = None
                                    break
                            
                            if value is not None:
                                # 替换引用为实际值
                                ref_str = f"{{upstream.{match}}}"
                                if isinstance(value, (dict, list)):
                                    # 如果是复杂类型，转换为JSON字符串
                                    text = text.replace(ref_str, json.dumps(value, ensure_ascii=False))
                                else:
                                    # 基本类型直接替换
                                    text = text.replace(ref_str, str(value))
                            else:
                                append_log(f"  警告: 无法解析上游引用 {{upstream.{match}}}\n")
                        except Exception as e:
                            append_log(f"  解析上游引用失败: {e}\n")
                    
                    return text
                
                if output_type == "display":
                    # 显示结果节点
                    show_bbox = node.properties.get("show_bounding_box", True)
                    show_labels = node.properties.get("show_labels", True)
                    
                    # 从上游结果中获取实际数据来生成显示
                    if upstream_value:
                        # 如果有上游值，使用上游值进行显示
                        node.return_value = upstream_value
                        append_log(f"  显示结果: 使用上游返回值\n")
                    else:
                        # 否则使用默认返回值
                        node.return_value = {"type": "display", "show_bbox": show_bbox, "show_labels": show_labels}
                        append_log(f"  显示结果: 使用默认设置\n")
                
                elif output_type == "file":
                    # 文件保存节点
                    output_path = node.properties.get("output_path", "./output.json")
                    
                    # 解析文件路径中的上游数据引用
                    output_path = resolve_upstream_refs(output_path, upstream_value)
                    
                    # 生成文件内容
                    content = "{}\n"
                    if upstream_value:
                        try:
                            content = json.dumps(upstream_value, ensure_ascii=False, indent=2)
                        except:
                            content = str(upstream_value) + "\n"
                    
                    append_log(f"  保存到文件: {output_path}\n")
                    try:
                        # 确保目录存在
                        os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True)
                        with open(output_path, 'w', encoding='utf-8') as f:
                            f.write(content)
                        append_log(f"  文件保存成功\n")
                        node.return_value = {"type": "file", "path": output_path, "saved": True}
                    except Exception as e:
                        append_log(f"  文件保存失败: {e}\n")
                        node.return_value = {"type": "file", "error": str(e)}
                
                elif output_type == "database":
                    # 数据库保存节点
                    db_name = node.properties.get("db_name", "face_db")
                    
                    # 解析数据库名称中的上游数据引用
                    db_name = resolve_upstream_refs(db_name, upstream_value)
                    
                    append_log(f"  保存到数据库: {db_name}\n")
                    # 实际应用中可以实现真正的数据库操作
                    node.return_value = {"type": "database", "db_name": db_name, "saved": True}
                
                elif output_type == "export":
                    # 导出节点
                    export_format = node.properties.get("export_format", "json")
                    export_path = node.properties.get("export_path", "./export.json")
                    
                    # 解析导出参数中的上游数据引用
                    export_format = resolve_upstream_refs(export_format, upstream_value)
                    export_path = resolve_upstream_refs(export_path, upstream_value)
                    
                    append_log(f"  导出数据: {export_path} (格式: {export_format})\n")
                    try:
                        # 确保目录存在
                        os.makedirs(os.path.dirname(os.path.abspath(export_path)), exist_ok=True)
                        with open(export_path, 'w', encoding='utf-8') as f:
                            if upstream_value:
                                json.dump(upstream_value, f, ensure_ascii=False, indent=2)
                            else:
                                json.dump({"message": "无数据可导出"}, f, ensure_ascii=False, indent=2)
                        append_log(f"  数据导出成功\n")
                        node.return_value = {"type": "export", "path": export_path, "format": export_format, "exported": True}
                    except Exception as e:
                        append_log(f"  数据导出失败: {e}\n")
                        node.return_value = {"type": "export", "error": str(e)}
                
                elif output_type == "message":
                    # 消息输出节点
                    message_format = node.properties.get("message_format", "{result}")
                    
                    # 解析消息格式中的上游数据引用
                    message_format = resolve_upstream_refs(message_format, upstream_value)
                    
                    # 尝试格式化消息
                    try:
                        # 如果有上游值，用上游值格式化
                        if upstream_value:
                            message = message_format.format(result=upstream_value)
                        else:
                            message = message_format
                    except Exception as e:
                        message = message_format  # 如果格式化失败，使用原始消息
                    
                    node.return_value = message
                    append_log(f"  消息输出: {message}\n")
                    
                    # 如果设置了显示对话框，则显示消息
                    if node.properties.get("show_dialog", False):
                        messagebox.showinfo("消息输出", str(message))
                    
                    # 如果设置了日志到控制台，则打印日志
                    if node.properties.get("log_to_console", True):
                        append_log(f"  [控制台日志] {message}\n")
            
            # 显示节点的返回值
            if node.return_value is not None:
                # 使用表格格式显示返回值
                if isinstance(node.return_value, dict):
                    append_log(f"  节点返回值:\n")
                    append_log(f"{result_formatter.format_result_as_table(node.return_value)}\n")
                else:
                    append_log(f"  节点返回值: {node.return_value}\n")
            else:
                append_log(f"  节点无返回值\n")
    
    append_log("\n流程执行完成！\n")
    append_log("\n流程执行总结：\n")
    
    # 显示每个节点的最终状态
    for node_id, node in canvas.current_flow.nodes.items():
        append_log(f"- 节点 {node.name}:\n")
        append_log(f"  类型: {node.node_type}\n")
        append_log(f"  使用上游返回值: {node.properties.get('use_upstream_value', False)}\n")
        # 使用表格格式显示最终返回值
        if isinstance(node.return_value, dict):
            append_log(f"  最终返回值:\n")
            append_log(f"{result_formatter.format_result_as_table(node.return_value)}\n")
        else:
            append_log(f"  最终返回值: {node.return_value}\n")
    
    # 禁用编辑
    result_text.config(state="disabled")
    
    # 添加关闭按钮
    ttk.Button(test_window, text="关闭", command=test_window.destroy).pack(pady=10)